天天看點

《重構改善既有代碼的設計》之重構清單--處理概括關系(二)五、Push Down Field(下移字段)六、Extract Subclass (提煉子類)七、Extract Superclass(提煉超類)八、Extract Interface (提煉接口)

五、Push Down Field(下移字段)

超類中的某個字段隻被部分(而非全部)子類用到。

将這個字段移到需要它的子類中去。

動機

Push Down Field 與Pull Up Field恰恰相反:如果隻有某些(而非全部)子類需要超類内的一個字段,你可以使用本項重構。

做法

1、在所有子類中聲明該字段。

2、将該字段從超類中移除。

3、編譯,測試。

4、将該字段從所有不需要它的那些子類中删掉。

5、編譯,測試。

六、Extract Subclass (提煉子類)

類中的某些特性隻被某些(而非全部)執行個體用到。

建立一個子類,将上面所說的那一部分特性移到子類中。

動機

使用Extract Subclass的主要動機是:你發現類中的某些行為隻被一部分執行個體用到,其他執行個體不需要它們。有時候這種行為上的差異是通過類型碼區分的,此時你可以使用Replace Type Code with Subclass或Replace Type Code with State/Strategy。但是,并非一定要出現了類型碼才表示需要考慮使用子類。

Extract Class是Extract Subclass之外的另一種選擇,兩者之間的抉擇其實就是委托和繼承之間的抉擇。Extract Subclass 通常更容易進行,但它也有限制:一旦對象建立完成,你無法再改變與類型相關的行為。但如果使用Extract Class,你隻需插入另一個元件就可以改變對象行為。此外,子類隻能用以表現一組變化。如果你需要一個類以幾種不同的方式變化,就必須使用委托。

做法

1、為源類定義一個新類。

2、為這個新類的子類提供構造函數。

? 簡單的做法是:讓子類構造函數接受與超類構造函數相同的參數,并通過super調用超類構造函數。

? 如果你希望對使用者隐藏子類的存在,可使用Replace Constructor with Factory Method。

3、找出調用超類構造函數的所有地點。如果它們需要的是建立的子類,令它們改而調用新構造函數。

? 如果子類構造函數需要的參數和超類構造函數不同,可以使用Rename Method 修改其參數列。如果子類構造函數不需要超類構造函數的某些參數,可以使用Rename Method将它們去除。

? 如果不再需要直接建立超類執行個體,就将超類聲明為抽象類。

4、逐一使用Push Down Method和Push Down Field将源類的特性移到子類去。

? 和Extract Class不同的是:先處理函數再處理資料,通常會簡單一些。

? 當一個public函數被下移到子類後,你可能需要重新定義該函數的調用端的局部變量或參數類型,讓它們改而去調用子類中的新函數。如果忘記進行這一步驟,編譯器會提醒你。

5、找到所有這樣的字段:它們所傳達的資訊如今可由繼承提醒自身傳達(這一類字段通常是boolean變量或類型碼)。以Self Encapsulate Field避免直接使用這些字段,然後将它們的取值函數替換為多态常量函數。所有使用這些字段的地方都 應該以Replace Conditional with Polymorphism重構。

? 任何函數如果位于源類之外,而又使用了上述字段的通路函數,考慮Move Method将它移到源類中,然後再使用Replace Conditional with Polymorphism。

6、每次下移之後,編譯并測試。

七、Extract Superclass(提煉超類)

兩個類有相似特性。

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

動機

重複代碼是系統中最糟糕的東西之一。如果你在不同地方做同一件事情,一旦需要修改那些動作,你就得平白做更多修改。

重複代碼的某種形式就是:兩個類以相同的方式做類似的事情,或者以不同的方式做類型的事情。對象提供了一種簡化這種情況的機制,那就是繼承。但是,在建立這些具有共通性的類之前,你往往無法發現這樣的共通性,是以經常會在具有共通性的類出現之後,再開始建立其間的繼承結構。

另一種選擇就是Extract Class。這兩種方案之間的選擇其實就是繼承和委托之間的選擇。如果兩個類可以共享行為,也可以共享接口,那麼繼承是比較簡單的做法。如果你選錯了,也總有Replace Inheritance with Delegation這瓶後悔藥可吃。

做法

1、為原本的類建立一個空白的抽象超類。

2、運用Pull Up Field 、Pull Up Method和Pull Up Constructor Body 逐一将子類的共同元素上移到超類。

? 先搬移字段,通常比較簡單。

? 如果相應的子類有不同的簽名,但用途相同,可以先使用Rename Method将它們簽名改為相同,然後再使用Pull Up Method。

? 如果相應的子類函數又相同的簽名,但函數本體不同,可以在超類中把它們的共同簽名聲明為抽象函數。

? 如果相應的子類有不同的函數本體,但用途相同,可以試着使用 Substitute Algorithm 把其中一個函數的函數本體複制到另一個函數中。如果運轉正常,你就可以使用Pull Up Method。

3、每次上移後,編譯并測試。

4、檢查留在子類中的函數,看它們是否還有共通部分。如果有,可以使用Extract Method将共通部分再提煉出來,然後使用Pull Up Method将提煉出來的函數上移到超類。如果各個子類中某個函數的整體流程很相似,你也許可以使用Form Template Method。

5、将所有共通元素都上移到超類之後,檢查子類的所有使用者。如果它們隻使用共同接口,你就可以把它們請求的對象類型改為超類。

八、Extract Interface (提煉接口)

若幹使用者使用類接口中的同一子集,或者兩個類的接口有部分相同。

将相同的子集提煉到一個獨立的接口中。

動機

類之間批次互用的方式有若幹種。“使用一個類”通常意味用到該類的所有責任區。另一種情況是,某一組客戶隻使用類責任區中一個特定子集。再一種情況則是,這個類需要與所有協助處理某些特定請求的類合作。

對于後兩種情況,将真正用到的這部分責任分離出來通常很有意義,因為這樣可以使系統的用法更清晰,同時也更容易看清系統的責任劃分。如果新的類需要支援上述子集,也比較能夠看清子集内有些什麼東西。

在許多面向對象語言中,這種責任時通過多繼承來實作的。你可以針對每組行為建立一個類,再将它們組合于同一實作中。Java提供單繼承,但你可以運用接口來昭示并實作上述需求。接口對于java程式的設計方式有着巨大影響,就連Smalltalk程式員都認為接口是一大進步!

Extract Superclass 和Extract Interface之間有些相似之處。Extract Interface 隻能提煉共通接口,不能提煉共通代碼。使用Extract Interface 可能造成難聞的“重複”壞味道,幸而你可以運用Extract Class 先把共通行為放進一個元件中,然後将工作委托該元件,進而解決這個問題。如果有不少共通行為,Extract Superclass會比較簡單,但是每個類隻能有一個超類。

如果某個類在不同環境下扮演截然不同的角色,使用接口就是一個好主意。你可以針對每個角色以Extract Interface提煉出相應接口。另一種可以用上Extract Interface的情況是:你想要描述的一個類的外部依賴接口(outbound interface,即這個類要求服務方提供的操作)。如果你打算将來加入其它種類的通路對象,隻需要他們實作這個接口即可。

做法

1、建立一個空接口。

2、在接口中聲明待提煉類的共通動作。

3、讓相關的類實作上述接口。

4、調整用戶端的類型聲明,令其使用該接口。、

繼續閱讀