:notebook: 本文已歸檔到:「blog」java
翻譯自:https://sourcemaking.com/refactoring/smells/oo-abusersgit
濫用面向對象(Object-Orientation Abusers)這組壞味道意味着:代碼部分或徹底地違背了面向對象編程原則。程序員
Switch 聲明(Switch Statements)github
你有一個複雜的
switch
語句或if
序列語句。算法
面向對象程序的一個最明顯特徵就是:少用 switch
和 case
語句。從本質上說,switch
語句的問題在於重複(if
序列也一樣如此)。你常會發現 switch
語句散佈於不一樣地點。若是要爲它添加一個新的 case
子句,就必須找到全部 switch
語句並修改它們。面向對象中的多態概念可爲此帶來優雅的解決辦法。編程
大多數時候,一看到 switch
語句,就應該考慮以多態來替換它。設計模式
提煉函數(Extract Method)
將 switch
語句提煉到一個獨立函數中,再以 搬移函數(Move Method)
將它搬移到須要多態性的那個類裏。switch
是基於類型碼來識別分支,這時能夠運用 以子類取代類型碼(Replace Type Code with Subclass)
或 以狀態/策略模式取代類型碼(Replace Type Code with State/Strategy)
。以多態取代條件表達式(Replace Conditional with Polymorphism)
了。以明確函數取代參數(Replace Parameter with Explicit Methods)
。引入 Null 對象(Introduce Null Object)
。switch
操做只是執行簡單的行爲,就沒有重構的必要了。switch
常被工廠設計模式族(工廠方法模式(Factory Method)
和抽象工廠模式(Abstract Factory)
)所使用,這種狀況下也不必重構。問題bash
你有一段代碼能夠組織在一塊兒。函數
void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
複製代碼
解決post
移動這段代碼到一個新的函數中,使用函數的調用來替代老代碼。
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
複製代碼
問題
你的程序中,有個函數與其所駐類以外的另外一個類進行更多交流:調用後者,或被後者調用。
解決
在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是舊函數徹底移除。
問題
你有一個不可變的類型碼,它會影響類的行爲。
解決
以子類取代這個類型碼。
問題
你有一個類型碼,它會影響類的行爲,但你沒法經過繼承消除它。
解決
以狀態對象取代類型碼。
問題
你手上有個條件表達式,它根據對象類型的不一樣而選擇不一樣的行爲。
class Bird {
//...
double getSpeed() {
switch (type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
case NORWEGIAN_BLUE:
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
throw new RuntimeException("Should be unreachable");
}
}
複製代碼
解決
將這個條件表達式的每一個分支放進一個子類內的覆寫函數中,而後將原始函數聲明爲抽象函數。
abstract class Bird {
//...
abstract double getSpeed();
}
class European extends Bird {
double getSpeed() {
return getBaseSpeed();
}
}
class African extends Bird {
double getSpeed() {
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
}
}
class NorwegianBlue extends Bird {
double getSpeed() {
return (isNailed) ? 0 : getBaseSpeed(voltage);
}
}
// Somewhere in client code
speed = bird.getSpeed();
複製代碼
問題
你有一個函數,其中徹底取決於參數值而採起不一樣的行爲。
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
複製代碼
解決
針對該參數的每個可能值,創建一個獨立函數。
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
複製代碼
問題
你須要再三檢查某對象是否爲 null。
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
複製代碼
解決
將 null 值替換爲 null 對象。
class NullCustomer extends Customer {
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ? order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
複製代碼
臨時字段(Temporary Field)的值只在特定環境下有意義,離開這個環境,它們就什麼也不是了。
有時你會看到這樣的對象:其內某個實例變量僅爲某種特定狀況而設。這樣的代碼讓人不易理解,由於你一般認爲對象在全部時候都須要它的全部變量。在變量未被使用的狀況下猜想當初設置目的,會讓你發瘋。 一般,臨時字段是在某一算法須要大量輸入時而建立。所以,爲了不函數有過多參數,程序員決定在類中建立這些數據的臨時字段。這些臨時字段僅僅在算法中使用,其餘時候卻毫無用處。 這種代碼很差理解。你指望查看對象字段的數據,可是出於某種緣由,它們老是爲空。
提煉類(Extract Class)
將臨時字段和操做它們的全部代碼提煉到一個單獨的類中。此外,你能夠運用 以函數對象取代函數(Replace Method with Method Object)
來實現一樣的目的。引入 Null 對象(Introduce Null Object)
在「變量不合法」的狀況下建立一個 null 對象,從而避免寫出條件表達式。問題
某個類作了不止一件事。
解決
創建一個新類,將相關的字段和函數從舊類搬移到新類。
問題
你有一個過長函數,它的局部變量交織在一塊兒,以至於你沒法應用提煉函數(Extract Method) 。
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation.
//...
}
}
複製代碼
解決
將函數移到一個獨立的類中,使得局部變量成了這個類的字段。而後,你能夠將函數分割成這個類中的多個函數。
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// copy relevant information from order object.
//...
}
public double compute() {
// long computation.
//...
}
}
複製代碼
問題
你須要再三檢查某對象是否爲 null。
if (customer == null) {
plan = BillingPlan.basic();
}
else {
plan = customer.getPlan();
}
複製代碼
解決
將 null 值替換爲 null 對象。
class NullCustomer extends Customer {
Plan getPlan() {
return new NullPlan();
}
// Some other NULL functionality.
}
// Replace null values with Null-object.
customer = (order.customer != null) ? order.customer : new NullCustomer();
// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
複製代碼
殊途同歸的類(Alternative Classes with Different Interfaces)
兩個類中有着不一樣的函數,卻在作着同一件事。
這種狀況每每是由於:建立這個類的程序員並不知道已經有實現這個功能的類存在了。
函數更名(Rename Method)
根據它們的用途從新命名。搬移函數(Move Method)
、 添加參數(Add Parameter)
和 令函數攜帶參數(Parameterize Method)
來使得方法的名稱和實現一致。提煉超類(Extract Superclass)
。這種狀況下,已存在的類就成了超類。問題
函數的名稱未能恰當的揭示函數的用途。
class Person {
public String getsnm();
}
複製代碼
解決
修改函數名。
class Person {
public String getSecondName();
}
複製代碼
問題
你的程序中,有個函數與其所駐類以外的另外一個類進行更多交流:調用後者,或被後者調用。
解決
在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是舊函數徹底移除。
問題 某個函數須要從調用端獲得更多信息。
class Customer {
public Contact getContact();
}
複製代碼
解決 爲此函數添加一個對象函數,讓改對象帶進函數所需信息。
class Customer {
public Contact getContact(Date date);
}
複製代碼
問題
若干函數作了相似的工做,但在函數本體中卻包含了不一樣的值。
創建單一函數,以參數表達哪些不一樣的值。
問題
兩個類有類似特性。
解決
爲這兩個類創建一個超類,將相同特性移至超類。
被拒絕的饋贈(Refused Bequest)
子類僅僅使用父類中的部分方法和屬性。其餘來自父類的饋贈成爲了累贅。
有些人僅僅是想重用超類中的部分代碼而建立了子類。但實際上超類和子類徹底不一樣。
以委託取代繼承(Replace Inheritance with Delegation)
消除繼承。提煉超類(Extract Superclass)
將全部超類中對於子類有用的字段和函數提取出來,置入一個新的超類中,而後讓兩個類都繼承自它。問題
某個子類只使用超類接口中的一部分,或是根本不須要繼承而來的數據。
解決
問題
兩個類有類似特性。
解決
爲這兩個類創建一個超類,將相同特性移至超類。