### Extract Hierarchy(提煉繼承體系)
你有某個class 做了太多(過多〕工作,其中一部分工作是以大量條件式完成的。
建立繼承體系,以一個subclass 表示一種特殊情況。

**動機(Motivation)**
在漸進式設計過程中,常常會有這樣的情況:一開始設計者只想「以一個class 實 現一個概念」;但隨著設計方案的演化,最后卻可能「一個class 實現了兩個、三 個乃至十個不同的概念」。一開始,你建立了這個簡單的class ;數天或數周之后, 你可能發現:只要加入一個標記(flag)和一兩個測試,就可以在另一個環境下使用這個class ;一個月之后你又發現了另一個這樣的機會;一年之后,這個class 就完全一團糟了:標記變量和條件式遍布各處。
當你遇到這種「瑞士小刀般」的class ,不但能夠開瓶開罐、砍小樹枝、在演示文稿會上打出激光強調重點……(簡直無所不能),你就需要一個好策略(亦即本項重構、將它的各個功能梳理并分開。不過,請注意,只有當條件邏輯(conditional logic)在對象的整個生命期間保持不變,本重構所導入的策略才適用。否則你可能必須在分離各種狀況之前先使用Extract Class。
Extract Hierarchy 是一項大型重構,如果你一天之內不足以完成它,不要因此失去勇氣。將一個極度混亂的設計方案梳理出來,可能需要數周甚至數月的時間。 你可以先進行本重構中的一些簡易步驟,稍微休息一下,再花數天編寫一些明顯有生產力的代碼。當你領悟到更多東西,再回來繼續本項重構的其他步驟——這些步驟將因為你的領悟而顯得更加簡單明了。
**作法(Mechanics)**
我們為你準備了兩組重構作法。第一種情況是:你無法確定變異(variations)應該是些什么(也就是說你無法確定原始class 中該有哪些條件邏輯〕。這時候你希望每次一小步地前進:
- 鑒別出一種變異(variation)。
- 如果這種變異可能在對象生命期內發生變化,就運用Extract Class 將它提煉為一個獨立的class 。
- 針對這種變異,新建一個subclass ,并對原始class 實施Replace Constructor with Factory Method。再修改factory method,令它返回適當的(相應的)subclass 實體。
- 將含有條件邏輯的函數,一次一個,遂一拷貝到subclass ,然后在明確情況下 (注:對subclass 明確,對superclass 不明確!),簡化這些函數。
- 如有必要隔離函數中的「條件邏輯」和「非條件邏輯」,可對superclass 實施Extract Method。
- 重復上述過程,將所有變異(variations;特殊情況)都分離出來,直到可以將superclass 聲明為抽象類(abstract class)為止。
- 刪除superclass 中的那些「被所有subclasses 覆寫」的函數(的本體),并將 它聲明為抽象函數(abstract class)。
如果你非常清楚原始class 會有哪些變異(variations),可以使用另一種作法:
- 針對原始class 的每一種變異(variation)建立一個subclass 。
- 使用Replace Constructor with Factory Method 將原始過class 的構造函數轉變成factory method ,并令它針對每一種變異返回適當的subclass 實體。
- 如果原始class 中的各種變異是以type code 標示,先使用 Replace Type Code with Subclasses ;如果那些變異在對象生命期間會改變, 請使用Replace Type Code with State/Strategy。
- 針對帶有條件邏輯的函數,實施 Replace Conditional with Polymorphism。如果并非整個函數的行為有所變化,而只是函數一部分有所變化,請先運用 Extract Method 將變化部分和不變部分隔開來。
**范例:(Example)**
這里所舉的例子是「變異并不明朗」的情況。你可以在 Replace Type Code with Subclasses、Replace Type Code with State/Strategy 和 Replace Conditional with Polymorphism 等重構結果之上,驗證「變異已經明朗」的情況下如何使用本項重構。
我們以一個電費計算程序為例。這個程序有兩個classes :表示「消費者」的Customer 和表示「計費方案」的BillingScheme,如圖12.11。

圖12.11 Customer 和BillingScheme
BillingScheme 使用大量條件邏輯來計算不同情況(變異)下的費用:冬季和夏季的電價不同,私宅用電、小型企業用電、社會救濟(包括殘障人士)用電的價格也不同。這些復雜的邏輯導致BillingScheme 變得復雜。
第一個步驟是,提煉出條件邏輯中經常出現的某種變異性。本例之中可能是「視用戶是否為殘障人士」而發生的變化。用于標示這種情況的可能是Customer、 BillingScheme 或其他地方的一個標記變量(flag)。
我們針對這種變異建立一個subclass 。為了使用這個subclass ,我們需要確保它被建立并且被使用。因此我們需要處理BillingScheme 構造函數:首先對它實施 Replace Constructor with Factory Method,然后在所得的factory method 中為殘障 人士增加一個條件子句,使它在適當時候返回一個DisablityBillingScheme對象。
然后,我們需要觀察BillingScheme 的其他函數,尋找那些隨著「用戶是否為殘障人士」而變化的行為。createBill() 就是這樣一個函數,因此我們將它拷貝到subclass (圖 12,12)。

圖12.12 為「殘障人士」添加一個subclass
現在,我們需要檢查subclass 中的createBill() 函數。由于現在我們可以肯定該 消費者是殘障人士,因此可以簡化這個函數。所以下列代碼:
~~~
if (disabilityScheme()) doSomething
~~~
可以變成:
~~~
doSomething
~~~
如果規定在「殘障人士用電」和「企業用電」之間只能擇一,那么我們的方案就可以避免在BusinessBillingScheme 中出現任何條件代碼。
實施本項重構時,我們希望將「可能變化」和「始終不變」的部分分開,為此我們可以使用 Extract Method 和Decompose Conditional。本例將對BillingScheme 各函數實施這兩項重構,直到「是否為殘障人士」的所有判斷都得到了適當處理。然后我們再以相同過程處理他種變異(例如「社會救濟用電」)。
然而,當我們處理第二種變異時,我們應該觀察「社會救濟用電」與「殘障人士用 電」有何不同。我們希望能夠為不同的變異(特殊情況)建立起這般函數:有著相同意圖,但針對不同的變異性(特殊情況)釆取不同的實際作為(譯注:這就是Template Method)。例如上述兩種變異情況下的稅額計算可能不同。我們希望確保兩個subclasses 中的相應函數有相同的簽名式(signature)。這可能意味我們必須修改DisablityBillingScheme,使得以將subclasses 整理一番。通常我們發現,面對更多變異時,這種「相仿之中略帶變化」的函數(similar and varying methods patterns)會使整個系統結構趨于穩定,使我們更容易添加后續更多變異。
- 譯序 by 侯捷
- 譯序 by 熊節
- 序言
- 前言
- 章節一 重構,第一個案例
- 起點
- 重構的第一步
- 分解并重組statement()
- 運用多態(Polymorphism)取代與價格相關的條件邏輯
- 結語
- 章節二 重構原則
- 何謂重構
- 為何重構
- 「重構」助你找到臭蟲(bugs)
- 何時重構
- 怎么對經理說?
- 重構的難題
- 重構與設計
- 重構與性能(Performance)
- 重構起源何處?
- 章節三 代碼的壞味道
- Duplicated Code(重復的代碼)
- Long Method(過長函數)
- Large Class(過大類)
- Long Parameter List(過長參數列)
- Divergent Change(發散式變化)
- Shotgun Surgery(散彈式修改)
- Feature Envy(依戀情結)
- Data Clumps(數據泥團)
- Primitive Obsession(基本型別偏執)
- Switch Statements(switch驚悚現身)
- Parallel Inheritance Hierarchies(平行繼承體系)
- Lazy Class(冗贅類)
- Speculative Generality(夸夸其談未來性)
- Temporary Field(令人迷惑的暫時值域)
- Message Chains(過度耦合的消息鏈)
- Middle Man(中間轉手人)
- Inappropriate Intimacy(狎昵關系)
- Alternative Classes with Different Interfaces(異曲同工的類)
- Incomplete Library Class(不完美的程序庫類)
- Data Class(純稚的數據類)
- Refused Bequest(被拒絕的遺贈)
- Comments(過多的注釋)
- 章節四 構筑測試體系
- 自我測試代碼的價值
- JUnit測試框架
- 添加更多測試
- 章節五 重構名錄
- 重構的記錄格式
- 尋找引用點
- 這些重構準則有多成熟
- 章節六 重新組織你的函數
- Extract Method(提煉函數)
- Inline Method(將函數內聯化)
- Inline Temp(將臨時變量內聯化)
- Replace Temp with Query(以查詢取代臨時變量)
- Introduce Explaining Variable(引入解釋性變量)
- Split Temporary Variable(剖解臨時變量)
- Remove Assignments to Parameters(移除對參數的賦值動作)
- Replace Method with Method Object(以函數對象取代函數)
- Substitute Algorithm(替換你的算法)
- 章節七 在對象之間搬移特性
- Move Method(搬移函數)
- Move Field(搬移值域)
- Extract Class(提煉類)
- Inline Class(將類內聯化)
- Hide Delegate(隱藏「委托關系」)
- Remove Middle Man(移除中間人)
- Introduce Foreign Method(引入外加函數)
- Introduce Local Extension(引入本地擴展)
- 章節八 重新組織數據
- Self Encapsulate Field(自封裝值域)
- Replace Data Value with Object(以對象取代數據值)
- Change Value to Reference(將實值對象改為引用對象)
- Replace Array with Object(以對象取代數組)
- Replace Array with Object(以對象取代數組)
- Duplicate Observed Data(復制「被監視數據」)
- Change Unidirectional Association to Bidirectional(將單向關聯改為雙向)
- Change Bidirectional Association to Unidirectional(將雙向關聯改為單向)
- Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數)
- Encapsulate Field(封裝值域)
- Encapsulate Collection(封裝群集)
- Replace Record with Data Class(以數據類取代記錄)
- Replace Type Code with Class(以類取代型別碼)
- Replace Type Code with Subclasses(以子類取代型別碼)
- Replace Type Code with State/Strategy(以State/strategy 取代型別碼)
- Replace Subclass with Fields(以值域取代子類)
- 章節九 簡化條件表達式
- Decompose Conditional(分解條件式)
- Consolidate Conditional Expression(合并條件式)
- Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
- Remove Control Flag(移除控制標記)
- Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件式)
- Replace Conditional with Polymorphism(以多態取代條件式)
- Introduce Null Object(引入Null 對象)
- Introduce Assertion(引入斷言)
- 章節十一 處理概括關系
- Pull Up Field(值域上移)
- Pull Up Method(函數上移)
- Pull Up Constructor Body(構造函數本體上移)
- Push Down Method(函數下移)
- Push Down Field(值域下移)
- Extract Subclass(提煉子類)
- Extract Superclass(提煉超類)
- Extract Interface(提煉接口)
- Collapse Hierarchy(折疊繼承關系)
- Form Template Method(塑造模板函數)
- Replace Inheritance with Delegation(以委托取代繼承)
- Replace Delegation with Inheritance(以繼承取代委托)
- 章節十二 大型重構
- 這場游戲的本質
- Tease Apart Inheritance(梳理并分解繼承體系)
- Convert Procedural Design to Objects(將過程化設計轉化為對象設計)
- Separate Domain from Presentation(將領域和表述/顯示分離)
- Extract Hierarchy(提煉繼承體系)
- 章節十三 重構,復用與現實
- 現實的檢驗
- 為什么開發者不愿意重構他們的程序?
- 現實的檢驗(再論)
- 重構的資源和參考資料
- 從重構聯想到軟件復用和技術傳播
- 結語
- 參考文獻
- 章節十四 重構工具
- 使用工具進行重構
- 重構工具的技術標準(Technical Criteria )
- 重構工具的實用標準(Practical Criteria )
- 小結
- 章節十五 集成
- 參考書目