java開發中濫用面向對象稍不注意就違背了編程原則

Java 設計模式 斯蒂芬朱cPlusPlus 2019-05-27

Switch 聲明

Switch 聲明(Switch Statements)你有一個複雜的 switch 語句或 if 序列語句。
java開發中濫用面向對象稍不注意就違背了編程原則

問題原因

面向對象程序的一個最明顯特徵就是:少用 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) 。

收益

  • 提升代碼組織性。
java開發中濫用面向對象稍不注意就違背了編程原則

何時忽略

  • 如果一個 switch 操作只是執行簡單的行為,就沒有重構的必要了。
  • switch 常被工廠設計模式族(工廠方法模式(Factory Method)和抽象工廠模式(Abstract Factory))所使用,這種情況下也沒必要重構。

重構方法說明

提煉函數(Extract Method)

問題

你有一段代碼可以組織在一起。

void printOwing() {
printBanner();
//print details
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}

解決

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

void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}

搬移函數(Move Method)

問題

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

java開發中濫用面向對象稍不注意就違背了編程原則

解決

在該函數最常引用的類中建立一個有著類似行為的新函數。將舊函數變成一個單純的委託函數,或是舊函數完全移除。

java開發中濫用面向對象稍不注意就違背了編程原則

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

問題

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

java開發中濫用面向對象稍不注意就違背了編程原則

解決

以子類取代這個類型碼。

java開發中濫用面向對象稍不注意就違背了編程原則

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

問題

你有一個類型碼,它會影響類的行為,但你無法通過繼承消除它。

java開發中濫用面向對象稍不注意就違背了編程原則

解決

以狀態對象取代類型碼。

java開發中濫用面向對象稍不注意就違背了編程原則

以多態取代條件表達式(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)的值只在特定環境下有意義,離開這個環境,它們就什麼也不是了。
java開發中濫用面向對象稍不注意就違背了編程原則

問題原因

有時你會看到這樣的對象:其內某個實例變量僅為某種特定情況而設。這樣的代碼讓人不易理解,因為你通常認為對象在所有時候都需要它的所有變量。在變量未被使用的情況下猜測當初設置目的,會讓你發瘋。 通常,臨時字段是在某一算法需要大量輸入時而創建。因此,為了避免函數有過多參數,程序員決定在類中創建這些數據的臨時字段。這些臨時字段僅僅在算法中使用,其他時候卻毫無用處。 這種代碼不好理解。你期望查看對象字段的數據,但是出於某種原因,它們總是為空。

解決方法

  • 可以通過 提煉類(Extract Class) 將臨時字段和操作它們的所有代碼提煉到一個單獨的類中。此外,你可以運用 以函數對象取代函數(Replace Method with Method Object) 來實現同樣的目的。
  • 引入 Null 對象(Introduce Null Object) 在“變量不合法”的情況下創建一個 null 對象,從而避免寫出條件表達式。
java開發中濫用面向對象稍不注意就違背了編程原則

收益

  • 更好的代碼清晰度和組織性。
java開發中濫用面向對象稍不注意就違背了編程原則

重構方法說明

提煉類(Extract Class)

問題

某個類做了不止一件事。

java開發中濫用面向對象稍不注意就違背了編程原則

解決

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

java開發中濫用面向對象稍不注意就違背了編程原則

以函數對象取代函數(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)兩個類中有著不同的函數,卻在做著同一件事。
java開發中濫用面向對象稍不注意就違背了編程原則

問題原因

這種情況往往是因為:創建這個類的程序員並不知道已經有實現這個功能的類存在了。

解決方法

  • 如果兩個函數做同一件事,卻有著不同的簽名,請運用 函數改名(Rename Method) 根據它們的用途重新命名。
  • 運用 搬移函數(Move Method) 、 添加參數(Add Parameter) 和 令函數攜帶參數(Parameterize Method) 來使得方法的名稱和實現一致。
  • 如果兩個類僅有部分功能是重複的,嘗試運用 提煉超類(Extract Superclass) 。這種情況下,已存在的類就成了超類。
  • 當最終選擇並運用某種方法來重構後,也許你就能刪除其中一個類了。

收益

  • 消除了不必要的重複代碼,為代碼瘦身了。
  • 代碼更易讀(不再需要猜測為什麼要有兩個功能相同的類)。
java開發中濫用面向對象稍不注意就違背了編程原則

何時忽略

  • 有時合併類是不可能的,或者是如此困難以至於沒有意義。例如:兩個功能相似的類存在於不同的 lib 庫中。

重構方法說明

函數改名(Rename Method)

問題

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

class Person {
public String getsnm();
}

解決

修改函數名。

class Person {
public String getSecondName();
}

搬移函數(Move Method)

問題

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

java開發中濫用面向對象稍不注意就違背了編程原則

解決

在該函數最常引用的類中建立一個有著類似行為的新函數。將舊函數變成一個單純的委託函數,或是舊函數完全移除。

java開發中濫用面向對象稍不注意就違背了編程原則

添加參數(Add Parameter)

問題 某個函數需要從調用端得到更多信息。

class Customer {
public Contact getContact();
}

解決 為此函數添加一個對象函數,讓改對象帶進函數所需信息。

class Customer {
public Contact getContact(Date date);
}

令函數攜帶參數(Parameterize Method)

問題

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

java開發中濫用面向對象稍不注意就違背了編程原則

解決

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

java開發中濫用面向對象稍不注意就違背了編程原則

提煉超類(Extract Superclass)

問題

兩個類有相似特性。

java開發中濫用面向對象稍不注意就違背了編程原則

解決

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

java開發中濫用面向對象稍不注意就違背了編程原則

被拒絕的饋贈

被拒絕的饋贈(Refused Bequest)子類僅僅使用父類中的部分方法和屬性。其他來自父類的饋贈成為了累贅。
java開發中濫用面向對象稍不注意就違背了編程原則

問題原因

有些人僅僅是想重用超類中的部分代碼而創建了子類。但實際上超類和子類完全不同。

解決方法

  • 如果繼承沒有意義並且子類和父類之間確實沒有共同點,可以運用 以委託取代繼承(Replace Inheritance with Delegation) 消除繼承。
  • 如果繼承是適當的,則去除子類中不需要的字段和方法。運用 提煉超類(Extract Superclass) 將所有超類中對於子類有用的字段和函數提取出來,置入一個新的超類中,然後讓兩個類都繼承自它。
java開發中濫用面向對象稍不注意就違背了編程原則

收益

  • 提高代碼的清晰度和組織性。
java開發中濫用面向對象稍不注意就違背了編程原則

重構方法說明

以委託取代繼承(Replace Inheritance with Delegation)

問題

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

java開發中濫用面向對象稍不注意就違背了編程原則

解決

  1. 在子類中新建一個字段用以保存超類;
  2. 調整子類函數,令它改而委託超類;
  3. 然後去掉兩者之間的繼承關係。
java開發中濫用面向對象稍不注意就違背了編程原則

提煉超類(Extract Superclass)

問題

兩個類有相似特性。

java開發中濫用面向對象稍不注意就違背了編程原則

解決

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

java開發中濫用面向對象稍不注意就違背了編程原則

相關推薦

推薦中...