開發中濫用面向對象,你是否違背了編程原則

Switch 聲明
Switch 聲明(Switch Statements)程序員

你有一個複雜的 switch 語句或 if 序列語句。算法

clipboard.png

問題緣由
面向對象程序的一個最明顯特徵就是:少用 switch 和 case 語句。從本質上說,switch 語句的問題在於重複(if 序列也一樣如此)。你常會發現 switch 語句散佈於不一樣地點。若是要爲它添加一個新的 case 子句,就必須找到全部 switch語句並修改它們。面向對象中的多態概念可爲此帶來優雅的解決辦法。設計模式

大多數時候,一看到 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,能夠運用 引入 Null 對象(Introduce Null Object) 。
收益
提高代碼組織性。this

clipboard.png

什麼時候忽略
若是一個 switch 操做只是執行簡單的行爲,就沒有重構的必要了。
switch 常被工廠設計模式族(工廠方法模式(Factory Method)和抽象工廠模式(Abstract Factory))所使用,這種狀況下也不必重構。
重構方法說明
提煉函數(Extract Method)
問題spa

你有一段代碼能夠組織在一塊兒。設計

void printOwing() {
printBanner();code

//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
解決orm

移動這段代碼到一個新的函數中,使用函數的調用來替代老代碼。對象

void printOwing() {
printBanner();
printDetails(getOutstanding());
}

void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
搬移函數(Move Method)
問題

你的程序中,有個函數與其所駐類以外的另外一個類進行更多交流:調用後者,或被後者調用。

clipboard.png

解決

在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是舊函數徹底移除。

clipboard.png

以子類取代類型碼(Replace Type Code with Subclass)
問題

你有一個不可變的類型碼,它會影響類的行爲。

clipboard.png

解決

以子類取代這個類型碼。

clipboard.png

以狀態/策略模式取代類型碼(Replace Type Code with State/Strategy)
問題

你有一個類型碼,它會影響類的行爲,但你沒法經過繼承消除它。

clipboard.png

解決

以狀態對象取代類型碼。

clipboard.png

以多態取代條件表達式(Replace Conditional with Polymorphism)
問題

你手上有個條件表達式,它根據對象類型的不一樣而選擇不一樣的行爲。

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();
以明確函數取代參數(Replace Parameter with Explicit Methods)
問題

你有一個函數,其中徹底取決於參數值而採起不一樣的行爲。

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 對象(Introduce Null Object)
問題

你須要再三檢查某對象是否爲 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)的值只在特定環境下有意義,離開這個環境,它們就什麼也不是了。

clipboard.png

問題緣由
有時你會看到這樣的對象:其內某個實例變量僅爲某種特定狀況而設。這樣的代碼讓人不易理解,由於你一般認爲對象在全部時候都須要它的全部變量。在變量未被使用的狀況下猜想當初設置目的,會讓你發瘋。 一般,臨時字段是在某一算法須要大量輸入時而建立。所以,爲了不函數有過多參數,程序員決定在類中建立這些數據的臨時字段。這些臨時字段僅僅在算法中使用,其餘時候卻毫無用處。 這種代碼很差理解。你指望查看對象字段的數據,可是出於某種緣由,它們老是爲空。

解決方法
能夠經過 提煉類(Extract Class) 將臨時字段和操做它們的全部代碼提煉到一個單獨的類中。此外,你能夠運用 以函數對象取代函數(Replace Method with Method Object) 來實現一樣的目的。
引入 Null 對象(Introduce Null Object) 在「變量不合法」的狀況下建立一個 null 對象,從而避免寫出條件表達式。

clipboard.png

收益
更好的代碼清晰度和組織性。

clipboard.png

重構方法說明
提煉類(Extract Class)
問題

某個類作了不止一件事。

clipboard.png

解決

創建一個新類,將相關的字段和函數從舊類搬移到新類。

clipboard.png

以函數對象取代函數(Replace Method with Method Object)
問題

你有一個過長函數,它的局部變量交織在一塊兒,以至於你沒法應用提煉函數(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 對象(Introduce Null Object)
問題

你須要再三檢查某對象是否爲 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)

兩個類中有着不一樣的函數,卻在作着同一件事。

clipboard.png

問題緣由
這種狀況每每是由於:建立這個類的程序員並不知道已經有實現這個功能的類存在了。

解決方法
若是兩個函數作同一件事,卻有着不一樣的簽名,請運用 函數更名(Rename Method) 根據它們的用途從新命名。
運用 搬移函數(Move Method) 、 添加參數(Add Parameter) 和 令函數攜帶參數(Parameterize Method) 來使得方法的名稱和實現一致。
若是兩個類僅有部分功能是重複的,嘗試運用 提煉超類(Extract Superclass) 。這種狀況下,已存在的類就成了超類。
當最終選擇並運用某種方法來重構後,也許你就能刪除其中一個類了。
收益
消除了沒必要要的重複代碼,爲代碼瘦身了。
代碼更易讀(再也不須要猜想爲何要有兩個功能相同的類)。

clipboard.png

什麼時候忽略
有時合併類是不可能的,或者是如此困難以致於沒有意義。例如:兩個功能類似的類存在於不一樣的 lib 庫中。
重構方法說明
函數更名(Rename Method)
問題

函數的名稱未能恰當的揭示函數的用途。

class Person {
public String getsnm();
}
解決

修改函數名。

class Person {
public String getSecondName();
}
搬移函數(Move Method)
問題

你的程序中,有個函數與其所駐類以外的另外一個類進行更多交流:調用後者,或被後者調用。

clipboard.png

解決

在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是舊函數徹底移除。

clipboard.png

添加參數(Add Parameter)
問題 某個函數須要從調用端獲得更多信息。

class Customer {
public Contact getContact();
}
解決 爲此函數添加一個對象函數,讓改對象帶進函數所需信息。

class Customer {
public Contact getContact(Date date);
}
令函數攜帶參數(Parameterize Method)
問題

若干函數作了相似的工做,但在函數本體中卻包含了不一樣的值。

clipboard.png

解決

創建單一函數,以參數表達哪些不一樣的值。

clipboard.png

提煉超類(Extract Superclass)
問題

兩個類有類似特性。

clipboard.png

解決

爲這兩個類創建一個超類,將相同特性移至超類。

clipboard.png

被拒絕的饋贈
被拒絕的饋贈(Refused Bequest)

子類僅僅使用父類中的部分方法和屬性。其餘來自父類的饋贈成爲了累贅。

clipboard.png

問題緣由
有些人僅僅是想重用超類中的部分代碼而建立了子類。但實際上超類和子類徹底不一樣。

解決方法
若是繼承沒有意義而且子類和父類之間確實沒有共同點,能夠運用 以委託取代繼承(Replace Inheritance with Delegation) 消除繼承。
若是繼承是適當的,則去除子類中不須要的字段和方法。運用 提煉超類(Extract Superclass) 將全部超類中對於子類有用的字段和函數提取出來,置入一個新的超類中,而後讓兩個類都繼承自它。

clipboard.png

收益
提升代碼的清晰度和組織性。

clipboard.png

重構方法說明
以委託取代繼承(Replace Inheritance with Delegation)
問題

某個子類只使用超類接口中的一部分,或是根本不須要繼承而來的數據。

clipboard.png

解決

在子類中新建一個字段用以保存超類;
調整子類函數,令它改而委託超類;
而後去掉二者之間的繼承關係。

clipboard.png

提煉超類(Extract Superclass)
問題

兩個類有類似特性。

clipboard.png

解決

爲這兩個類創建一個超類,將相同特性移至超類。

clipboard.png
圖片描述

相關文章
相關標籤/搜索