開發中濫用物件導向,你是否違背了程式設計原則
Switch 宣告
Switch 宣告(Switch Statements)
你有一個複雜的
switch
語句或if
序列語句。
問題原因
物件導向程式的一個最明顯特徵就是:少用
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)
。
收益
- 提升程式碼組織性。
何時忽略
-
如果一個
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)
問題
你的程式中,有個函式與其所駐類之外的另一個類進行更多交流:呼叫後者,或被後者呼叫。
解決
在該函式最常引用的類中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式,或是舊函式完全移除。
以子類取代型別碼(Replace Type Code with Subclass)
問題
你有一個不可變的型別碼,它會影響類的行為。
解決
以子類取代這個型別碼。
以狀態/策略模式取代型別碼(Replace Type Code with State/Strategy)
問題
你有一個型別碼,它會影響類的行為,但你無法通過繼承消除它。
解決
以狀態物件取代型別碼。
以多型取代條件表示式(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)的值只在特定環境下有意義,離開這個環境,它們就什麼也不是了。
問題原因
有時你會看到這樣的物件:其內某個例項變數僅為某種特定情況而設。這樣的程式碼讓人不易理解,因為你通常認為物件在所有時候都需要它的所有變數。在變數未被使用的情況下猜測當初設定目的,會讓你發瘋。 通常,臨時欄位是在某一演算法需要大量輸入時而建立。因此,為了避免函式有過多引數,程式設計師決定在類中建立這些資料的臨時欄位。這些臨時欄位僅僅在演算法中使用,其他時候卻毫無用處。 這種程式碼不好理解。你期望檢視物件欄位的資料,但是出於某種原因,它們總是為空。
解決方法
-
可以通過
提煉類(Extract Class)
將臨時欄位和操作它們的所有程式碼提煉到一個單獨的類中。此外,你可以運用以函式物件取代函式(Replace Method with Method Object)
來實現同樣的目的。 -
引入 Null 物件(Introduce Null Object)
在“變數不合法”的情況下建立一個 null 物件,從而避免寫出條件表示式。
收益
- 更好的程式碼清晰度和組織性。
重構方法說明
提煉類(Extract Class)
問題
某個類做了不止一件事。
解決
建立一個新類,將相關的欄位和函式從舊類搬移到新類。
以函式物件取代函式(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)
兩個類中有著不同的函式,卻在做著同一件事。
問題原因
這種情況往往是因為:建立這個類的程式設計師並不知道已經有實現這個功能的類存在了。
解決方法
-
如果兩個函式做同一件事,卻有著不同的簽名,請運用
函式改名(Rename Method)
根據它們的用途重新命名。 -
運用
搬移函式(Move Method)
、新增引數(Add Parameter)
和令函式攜帶引數(Parameterize Method)
來使得方法的名稱和實現一致。 -
如果兩個類僅有部分功能是重複的,嘗試運用
提煉超類(Extract Superclass)
。這種情況下,已存在的類就成了超類。 - 當最終選擇並運用某種方法來重構後,也許你就能刪除其中一個類了。
收益
- 消除了不必要的重複程式碼,為程式碼瘦身了。
- 程式碼更易讀(不再需要猜測為什麼要有兩個功能相同的類)。
何時忽略
- 有時合併類是不可能的,或者是如此困難以至於沒有意義。例如:兩個功能相似的類存在於不同的 lib 庫中。
重構方法說明
函式改名(Rename Method)
問題
函式的名稱未能恰當的揭示函式的用途。
class Person { public String getsnm(); }
解決
修改函式名。
class Person { public String getSecondName(); }
搬移函式(Move Method)
問題
你的程式中,有個函式與其所駐類之外的另一個類進行更多交流:呼叫後者,或被後者呼叫。
解決
在該函式最常引用的類中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式,或是舊函式完全移除。
新增引數(Add Parameter)
問題 某個函式需要從呼叫端得到更多資訊。
class Customer { public Contact getContact(); }
解決 為此函式新增一個物件函式,讓改物件帶進函式所需資訊。
class Customer { public Contact getContact(Date date); }
令函式攜帶引數(Parameterize Method)
問題
若干函式做了類似的工作,但在函式本體中卻包含了不同的值。
解決
建立單一函式,以參數列達哪些不同的值。
提煉超類(Extract Superclass)
問題
兩個類有相似特性。
解決
為這兩個類建立一個超類,將相同特性移至超類。
被拒絕的饋贈
被拒絕的饋贈(Refused Bequest)
子類僅僅使用父類中的部分方法和屬性。其他來自父類的饋贈成為了累贅。
問題原因
有些人僅僅是想重用超類中的部分程式碼而建立了子類。但實際上超類和子類完全不同。
解決方法
-
如果繼承沒有意義並且子類和父類之間確實沒有共同點,可以運用
以委託取代繼承(Replace Inheritance with Delegation)
消除繼承。 -
如果繼承是適當的,則去除子類中不需要的欄位和方法。運用
提煉超類(Extract Superclass)
將所有超類中對於子類有用的欄位和函式提取出來,置入一個新的超類中,然後讓兩個類都繼承自它。
收益
- 提高程式碼的清晰度和組織性。
重構方法說明
以委託取代繼承(Replace Inheritance with Delegation)
問題
某個子類只使用超類介面中的一部分,或是根本不需要繼承而來的資料。
解決
- 在子類中新建一個欄位用以儲存超類;
- 調整子類函式,令它改而委託超類;
- 然後去掉兩者之間的繼承關係。
提煉超類(Extract Superclass)
問題
兩個類有相似特性。
解決
為這兩個類建立一個超類,將相同特性移至超類。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69917606/viewspace-2643797/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 開閉原則——物件導向程式設計原則物件程式設計
- 物件導向設計原則物件
- Java中物件導向的設計原則Java物件
- The Principles of OOD 物件導向設計原則物件
- 2.物件導向設計原則物件
- 物件導向設計原則和模式物件模式
- 物件導向的基本設計原則物件
- 前端設計模式(0)物件導向&&設計原則前端設計模式物件
- 物件導向設計原則&設計模式分類物件設計模式
- 物件導向的編碼設計原則物件
- 實驗1:UML與物件導向程式設計原則物件程式設計
- 程式碼壞味道之濫用物件導向物件
- 物件導向程式設計(OOP)的七大原則物件程式設計OOP
- 翻譯 | The Principles of OOD 物件導向設計原則物件
- 物件導向之六大設計原則物件
- 物件導向設計的6大原則物件
- 物件導向程式設計的基本原則物件程式設計
- 物件導向之 開閉原則物件
- SOLID:物件導向設計的前五項原則Solid物件
- 61條物件導向設計的經驗原則物件
- 七種常見的物件導向設計原則物件
- Swift 中的面向協議程式設計:是否優於物件導向程式設計?Swift協議程式設計物件
- 物件導向OO原則物件
- 物件導向設計的六大原則(SOLID原則)-——里氏替換原則物件Solid
- 物件導向程式設計物件程式設計
- Python物件導向程式設計Python物件程式設計
- 程式設計思想 物件導向程式設計物件
- js物件導向程式設計JS物件程式設計
- 十三、物件導向程式設計物件程式設計
- 十六、物件導向程式設計物件程式設計
- Python 物件導向程式設計Python物件程式設計
- 物件導向設計的11原則--你稱得上OO專家麼?(專自CSDN)物件
- JS物件導向程式設計(一):物件JS物件程式設計
- 物件導向的7大原則與23種設計模式物件設計模式
- 物件導向程式設計,看這篇就夠了物件程式設計
- 物件導向設計的六大設計原則(附 Demo & UML類圖)物件
- go 併發程式設計案例三 golang 中的物件導向程式設計Golang物件
- 物件導向程式設計C++物件程式設計C++