1. 程式人生 > >《重構--改善既有程式碼的設計》 --MartinFowler

《重構--改善既有程式碼的設計》 --MartinFowler


《重構--改善既有程式碼的設計》 --Martin Fowler

重構定義: 在不改變軟體可觀察行為的前提下,對軟體內部進行調整(使用重構手法),以提高其可理解性,降低其修改成本。 -- 重點在兩點: 1. 不改變軟體的可觀察行為。2. 提高其可理解性。

兩個目的:1. 新增新功能。 2. 重構。  --重構就只管修改程式結構,不要新增新功能。 新增新功能就不要修改既有程式碼。兩者混合進行會使得程式朝不可理解的方向發展。

ps: 重構與設計模式具有辯證的關聯性,模式是目的,重構是到達之路。重構促進設計模式的形成與穩定,模式為重構提供前進方向,二者相輔相成,具有統一性。


為什麼需要重構: 
1. 面對迅速變化的需求,對原有的程式碼進行修改十分困難(邏輯複雜,條理不清晰,很難兼顧;改動介面多,測試困難), 尤其對於某些無法限定影響面的介面修改。
2. 使得原有設計保持本真意義。程式碼結構的流失具有累積性,原有的設計及意圖難以保持,閱讀原始碼很難理解原來的設計。
3. 消除重複程式碼,重複程式碼越多,修改的風險越大,修改帶來的不一致性可能越大(修改一處,未修改另一處)
4. 使得軟體更容易理解,結構更清晰,程式碼更簡潔。
5. 幫助找到bug
6. 提高程式設計速度    -- 維持良好設計,清晰的意圖,從而使得程式設計更加容易。
7. 重構與新功能    -- 如果你發現自己需要新增一個特性,而程式碼結構使你無法很方便地達成目的,那就先重構那個程式,使特性比較容易進行,然後再新增特性。
8. 重構與效能        -- 重構調整程式碼結構,使效能調優更加方便的進行。

何時重構:
1. 三次法則            --程式碼忍受了3次,就不要再忍受了,重構吧!
2. 新增新特性時候重構    -- 新特性的新增需要修改原先程式碼,且修改方案複雜
3. 複審程式碼時重構        -- 程式碼評審 談談如何重構
4. 重複程式碼過多
5. 函式過長            -- 一個函式應該只包含一個獨立的邏輯功能。 這樣將邏輯單元打散,可以減少程式碼耦合,並且更加清晰。
6. 過大的類            -- 一個類中出現太多例項變數,容易出現重複混亂的程式碼,可以考慮重構出子類。
7. 引數列表過長

壞程式碼的味道(什麼樣的形式才是壞程式碼):
重複程式碼
過長函式
過大的類
發散式變化: -- 一個類收到多個因素的影響,將朝著多個不同的方向變化,可以將類拆解成多個不同的類,然後橋接起來。
散彈式變化: -- 一個類的變動會印象多個其他的類,則可以將受此類影響的程式碼都放入這個類中。
同步式變化: -- 兩個東西總是一起變化,如引用與資料,則將這兩個同時變化的東西放入同一個類中。
資料泥團:   -- 一些零散的資料項出現在許多不同的類,或者函式的引數列表中。則可以將這些相同資料項抽取到一個類中。
基本型別偏執 -- 對於可以運用物件來表述一個物體,不用基本型別。如日期,時間等
多型擴充套件:   -- 替換switch case 根據型別多型擴充套件。
平行繼承體系  -- 如果為一個類增加子類,必須為另一個類增加子類。可讓一個體系引用另一個體系例項
冗贅類        -- 一個類沒有多大的實際意義,沒必要讓人花時間、精力來了解
誇誇奇談的未來性 -- 某些程式碼沒有多大作用,只是覺得未來可能有用,從而使得系統更難維護和理解
令人迷惑的暫時欄位 -- 某些欄位只在特殊情況下才被使用到,讓人不知其設定目的
過長的呼叫鏈路 -- 客戶端需要很長的呼叫鏈來獲取一個值,可以通過委託來獲取最終值
中間人        -- 存在過多不必要的委託
狎暱關係        -- 類之間的呼叫關係較多,依賴較強。可以將一些方法或欄位,移動到需要的類中。
異曲同工的類    -- 兩個函式做同一件事情,卻有著不同的簽名。可以根據用途來命名,並抽離重複程式碼。
不完美的類庫     -- 類庫缺少需要的功能
純稚的資料類     -- 未對資料進行封裝,資料介面暴露
被拒絕的遺贈    -- 子類完全未使用父類的程式碼
過多的註釋    -- 讓程式碼自身具有註釋功能

重構難題:
1. 資料庫改動        -- 可以增加資料庫接入層,使得資料庫的變化和業務的變化分離
2. 介面改動        -- 對於已經發布的介面,並被不可控系統使用的時候,則不能重構介面
3. 設計的改動        -- 對系統已有的設計進行重構,不如重新設計並實現。
4. 專案十分緊張    -- 對於業務十分緊張的專案,不應該重構。

如何重構:
-- 如何面對不斷變化的世界,如何面對不斷變化的需求。 只有深層次的抽象出不變的物件,然後將變化東西放在葉子類中,與其維護的資料結構同在, 通過繼承與多型來應對這種變化。

1. 可靠的測試環境。 --構建自動化單元測試套件(首推TestNG,儘量做到邏輯、邊界全覆蓋, 預期丟擲的異常也要測試),並有獨立的團隊進行功能(黑盒)測試,測試出現bug再通過單元測試定位bug。ps:在寫單測的過程中可以適當關注下程式碼執行效能。
2. 以微小的步伐修改程式。犯錯可以很容易發現,配合可靠的測試。 重構節奏: 小修改,測試;小修改,測試;小修改,測試........   -- 重構十分強調微小的修改步伐, 便於測試和審查,以減少引發bug的風險
3. 在重構函式內部修改變數名稱,清晰貼切。
4. 函式應該與它所使用的資料結構在同一物件內。
5. 查詢引用    -- 通過文字工具查詢你需要修改地方的引用,同時利用編譯器發現錯誤。對於被反射引用的地方,一定要記錄並進行詳細的測試。

重構手法:
一、重新組織函式
1. 提煉函式    Extract Method
    * 概述:將一段程式碼放入一個獨立函式中,並讓函式名稱解釋該函式的用途。
    * 動機:厭惡過長的程式碼。如果函式過長,則很難被理解(用途不清晰,需要大段的註釋描述意圖),測試也困難;相反,如果一個函式夠小、有清晰意圖的命名,則被複用的機會更大,函式也更容易被修改。
    * 做法:編寫一個新函式,根據這個函式的意圖來命名  -- 如果能對一段程式碼給予一個更好表達其意圖的命名,都可以提煉她。
    * 注意:這個重構手法最困難的地方在於提煉區域性變數:
            1. 如果需要讀取源函式的區域性變數,則可將其作為引數傳入新函式。
            2. 如果某些區域性變數需要在目標函式後面使用,可能需要新函式返回該區域性變數改變的值。
            3. 對於需要修改多個區域性變數的情況,則可不提煉此段程式碼,或者將所有臨時變數抽取為一個類。

2. 行內函數    Inline Method
    * 概述:在函式呼叫點插入函式本體,然後移除該函式
    * 動機:1.有些函式的內部程式碼比函式名本身更加清晰易讀,則可以內聯。2.函式呼叫混亂,可以先全部內聯在一起,然後重新提煉。
    * 做法:檢查多型覆蓋,檢查函式引用,引用點直接替換為函式。

3. 內聯臨時變數    Inline Temp
    * 概述:將所有對該變數的引用動作,替換為對她賦值表示式。
    * 動機:臨時變數可能妨礙其他的內聯手法。
    * 做法:確保只被一個簡單的表示式賦值了一次

4. 以查詢取代臨時變數    Replace Temp with Query
    * 概述:程式以某個臨時變數儲存表示式的運算結果。則將一個表示式提煉到一個函式中,將將對這個臨時變數的引用替換為對新函式的呼叫
    * 動機:臨時變數總是驅使你寫更長的函式,因為臨時變數只在函式內可以訪問到,所以將一些運算得到的臨時變數提煉為一個函式,可以在任何時候訪問。ps: 導致重複計算,但是使程式碼清晰。
    * 做法:確保該臨時變數只被賦值一次,然後用新函式替換引用處。可以先將該臨時變數宣告為final,讓編譯器保證只賦值一次。

ps: 1,2,3,4的操作都讓程式碼結構變得更加清晰、簡潔,使得函式名稱本身便具有自注釋功能。

5. 引入解釋性變數    Introduce Explaining Variable
    * 概述:將複雜表示式(或其中一部分)的結果放入一個臨時變數,以這個變數的名稱來解釋表示式用於
    * 動機:將一個複雜的表示式進行拆解,用臨時變數的名稱來表達部分過程的意圖。

6. 分解臨時變數    Split Temporary Variable
    * 概述:程式中某個臨時變數有多個用途,既不是迴圈變數,也不是收集計算結果變數,則應該對每個用途編寫一個臨時變數
    * 動機:臨時變數有不同的用途,或者由不同含義的操作計算而得到,則不應該使用同一臨時變數。

ps: 5,6的拆解都是為了使得表示式的意圖更加清晰,各種複用臨時變數,複雜計算過程等程式碼都可能讓程式碼變得含混。

7. 移除對引數的賦值    Remove Assignments to Parameters
    * 概述:程式碼對一個引數進行賦值,應該用一個臨時變數取代引數的位置。
    * 動機:對引數的賦值可能改變其引用的物件(引用引數), 從而丟失了原來的入參引用。所以將引數賦值給一個臨時變數以改變這種含混性。

8. 以函式物件取代函式    Replace Method with Method Object
    * 概述: 有一個大型函式,其中過多的區域性變數無法進行函式提煉,則將這個放入一個單獨物件中,這樣區域性變數就成了物件的成員變數,然後在物件內將大型函式分解為多個小函式
    * 動機: 區域性變數太複雜,根本無法拆解, 則使用一個物件來描述該方法。    -- 該物件需要有一個與方法意圖對應的命名。
    * 做法:    1. 建立一個新類,並將原類作為該類的一個常量引用
            2. 新類中提供建構函式接收原物件,及原函式的所有引數。
            3. 在新類中提供一個函式,將原函式的程式碼複製到其中,通過原物件引用相應變數。

9. 替換演算法    Substitute Algorithm
    * 概述:將函式本體替換為另一個演算法
    * 動機:原函式的演算法實現比較複雜,切不清晰,或者效能不好。如果你發現有更好的實現方式,勇敢的壯士斷腕,替換該演算法吧。


二、在物件間搬移特性
1. 搬移函式    Move Method
    * 概述:將一個函式移動到另一個類中去
    * 動機:類中一個函式與其他的類進行頻繁的交流,而與本類交流較少,則將函式移到另一個類中,從而減少呼叫關係。
    * 做法:搬移一個或者一組相關的函式;檢查源類的子類和超類是否有該函式的其他宣告(覆蓋,委託等問題);修改源函式使之成為一個委託函式。

2. 搬移欄位    Move Field
    * 概述:將類中一個欄位搬移到其他類中
    * 動機:本類中的一個欄位,被其他類中的欄位使用次數更多,搬移欄位減少呼叫關係,間接提高資訊隱藏性。
    * 做法:如果欄位被很多地方引用,則先使用自我封裝(Self-Encapsulation)將欄位變成設/取值函式,然後搬移欄位,最後修改設/取值函式為一個委託。

3. 提煉類    Extract Class
    * 概述:建立一個新的類,將相關的欄位和函式從舊類中搬移到新類
    * 動機:1. 一個類的方法過多、責任過大做了由兩個類做的事情。主要是由於當前類對於場景的抽象不夠細緻,導致過於複雜。
           2. 一個類中的數個特性朝著不同方向發展,從而變得無所關聯。最主要的表現是子類化,某些特性需以一種方式子類化,某些特性以另一種方式子類化    
    * 做法:從一個類中搬移函式和欄位到一個新類中,使得新類作為一個事物的更細緻抽象。
    * 例如:一個person類有地址欄位和函式,比較複雜。可以將地址抽象為一個新類,然後person呼叫這個地址類。

4. 內聯類    Inline Class
    * 概述: 將一個類的所有特性搬移到另一個類中,然後移除該類
    * 動機: 這個類沒有做太多事情
    * 做法: 上述的反過程

5. 隱藏委託關係    Hide Delegate
    * 概述: 在服務類建立客戶所需的所有函式,用以隱藏委託關係
    * 動機: 對內部進行封裝,通過一個委託類來呼叫其他物件,這樣呼叫者可以不用瞭解內部變化,從而降低修改風險。
    * 做法: 正如設計模式中的代理模式等, 不僅隱藏呼叫關係,還可以對呼叫進行統計等管理。

6. 移除中間人    Remove Middle Man
    * 概述: 直接呼叫受託類
    * 動機: 某個類做了過多的簡單委託,每新增一個新特性都需要在委託類中進行相應新增。
    * 做法: 上述反過程

7. 引入外加函式    Introduce Foreign Method
    * 概述: 在客戶類中建立一個函式,並以第一引數形式傳入一個服務類例項
    * 動機: 由於程式碼修改權不在自己手裡,但是需要為某個類新增新特性。可以在外層寫這樣一個函式,並所需要的值都以引數傳入。
    * 做法: 在擁有程式碼所有權後,將此函式返回她該去的地方

8. 引入本地擴充套件    Introduce Local Extension
    * 概述: 當需要為一個服務類提供一些額外的函式,但是你無法修改。可建立一個新類,使她包含這些額外的函式。讓這個擴充套件類成為源類的子類或者包裝類
    * 動機: 為類做擴充套件,可以使用裝飾模式或者繼承,使用繼承請確保必定有繼承關係。

ps: 從以上重構方法可以看出,其最終都是為了減少了程式碼中的呼叫關係,提高程式碼的意圖表達。

三、 重新組織資料
1. 自封裝欄位    &封裝欄位    &封裝集合        Self Encapsulate Field & Encapsulate Field & Encapsulate Collection
    * 概述:將欄位進行封裝,然後提供設/取值函式,對於集合取值只能返回可讀的副本
    * 動機:隱藏內部資料介面,這樣就算欄位資料結構發生變化,只需要調整設/取值函式,從而不影響其他類的呼叫; 對於集合需要防止外部對引用的修改; 方便上syncrized關鍵字

2. 以物件取代資料值    Replace Data Value with Object
    * 概述:將資料項變為欄位
    * 動機:資料項為對一個事物屬性的描述,你無法估量這個場景下該事物屬性的複雜性,所以用一個類構建該資料項,能夠抗擊未來的變化。

3. 將值物件改為引用物件    Change Value to Reference
    * 概述:將這個值物件變成引用物件
    * 動機:對一個物件修改的資料,能夠影響引用這個物件的地方

4. 將物件引用改為值物件    Change Reference to Value
    * 概述:將引用物件變為一個值物件
    * 動機:利用其不可變性質

5. 用物件取代陣列    Replace Array with Object
    * 概述:以物件替換陣列,對於陣列中的每個元素,以一個欄位來表示
    * 動機:有些其他語言,將一個物件的屬性存放在一個數組中了。將其重構為物件,從而可用欄位名逐一描述

6. 複製被監視資料    Duplicate Observed Data
    * 概述:將資料複製到一個領域物件中,建立一個observer模式,用於同步領域物件和GUI物件內的資料
    * 動機:分離介面顯示和業務邏輯,在介面更新資料的時候同步更新領域物件資料

7. 將單向關聯改為雙向關聯    Change Unidirectional Association to Bidirectional
    * 概述:新增一個反向指標,並使修改函式能夠同時更新兩條連線
    * 動機:使得兩個類相互依賴,方便訪問對方的成員。 雖然方便,但是呼叫關係複雜,容易產生殭屍物件,修改時相互影響。

8. 將雙向關聯改為單項關聯    Change Bidirectional Association to Unidirectional
    * 概述:去掉不必要的關聯
    * 動機:降低類間複雜度

9. 以字面常量取代魔法數    Replace Magic Number with Symbolic Constant
    * 概述:創造一個常量,根據其意義命名,並將有特殊意義的字面數值替換為這個常量
    * 動機:魔數-有特殊意義的數字,使用一個常量來表示這個數字的意義

10. 以資料類取代記錄    Replace Record with Data Class
    * 概述:面對傳統程式設計的記錄結構,為該記錄建立一個'啞'資料物件
    * 動機:與資料庫的記錄進行互動,ORM

11. 以類取代型別碼    Replace Type Code with Class
    * 概述:用一個新的類替換該類中用常量數值表示的型別
    * 動機:其實就是使用列舉型別來代替常量列舉,這樣使得型別被更有意義的描述,並且提供了對應的操作函式,對於複雜的型別十分方便。

12. 以子類取代型別碼    Replace Type Code with Subclasses
    * 概述:為型別碼建立一個繼承宿主類的子類,將根據這個型別碼執行的操作,放入到一個子類中去。
    * 動機:將不同型別碼的操作都放入對應的子類中,這樣不經讓程式碼更清晰,同時防止錯亂。

13. 用狀態或者策略取代型別碼    Replace Type Code with State/Strategy
    * 概述: 類中有型別碼會影響類的行為,但是無法通過繼承手法來消除
    * 動機: 無法直接繼承宿主類,則自己定義一個抽象的類去繼承,與上述一致。

14. 以欄位取代子類    Replace Subclass with Fields
    * 概述: 各個子類的差別不是打,只在一些常量資料上
    * 動機: 將子類中的共同行為搬移到超類,並在超類中定義這組差異的常量。減少無必要的編碼

四、 簡化條件表示式
1. 分解條件表示式        Decompose Conditional
    * 概述:有一個複雜的條件表示式,從if then else中分別提煉出獨立函式
    * 動機:複雜的條件邏輯常常導致程式碼複雜度的提高,提煉不同分支,並按照意圖命名,是程式碼可讀性和清晰提高

2. 合併條件表示式        Consolidate Conditional Expression
    * 概述:有一些列的條件測試,但是都返回相同結果,則合併這些表示式
    * 動機:有一些表示式雖然條件不同,但是結果一樣,將這些條件合併

3. 合併重複的條件片段    Consolidate Duplicate Conditional Fragment
    * 概述:條件表示式的每個分支有相同的一段程式碼,將這些程式碼抽取出來,放入表示式之外
    * 動機:將每個分支都執行的程式碼,搬移到程式碼執行分支之外,簡化分支表示式,以使程式碼更加清晰。

4. 以衛語句取代巢狀條件表示式    Replace Nested Conditional with Guard Clauses
    * 概述:用衛語句處理特殊情況,衛語句就是if then return這種形式的表示式,常用於函式入口處,保護函式體只接受正確的引數。
    * 動機:對於情況比較特使的邏輯,使用此表示式可以簡化程式碼,使之更加清晰

5. 以多型取代條件表示式    Replace Conditional with Polymorphism
    * 概述:將條件表示式的分支,放入每個子類的覆寫函式中,並將原始函式宣告為abstract
    * 動機:子類覆蓋超類的條件表示式,在子類中只關注與自己相關的條件和行為。這樣將集中在一起的邏輯打散到各個子類中。

6. 引入Null物件        Introduce Null Object
    * 概述:當需要再三檢查物件是否為null,則將null值替換為一個null物件
    * 動機:為原類建立一個子類,其行為就是原類的null版本,可以方便多型的進行。空物件的存在,可以省去判空邏輯(判空邏輯只寫在Null物件內部,其他寫在超類)。

7. 引入斷言            Introduece Assertion
    * 概述:某段程式碼需要對程式狀態做出某種假設,以斷言明確表現這種假設
    * 動機:斷言可以幫助閱讀程式程式碼所做的假設,可以在除錯和除錯,測試中廣泛使用

ps: 這些方法都為了減少條件表示式的複雜性,提高其清晰度和意圖,使用多型和空物件的方式,在一定程度上為程式設計提供方便。

五、 簡化函式呼叫
1.  函式改名        Rename Method
    * 概述:修改函式名稱
    * 動機:函式的名稱未能表示函式的意圖

2. 增加引數        Add Parameter
    * 概述:為函式新增一個物件引數,讓這個物件帶進函式所需的資訊
    * 動機:某個函式需要從呼叫端獲取更多的資訊

3. 移除引數         Remove Parameter
    * 概述:將函式引數移除
    * 動機:函式本體不在需要某個引數,而引數列表卻有

4. 將查詢函式和修改函式分離        Separate Query from modifier
    * 概述:建立兩個不同的函式,其中一個負責修改,另一個負責查詢
    * 動機:獲取值得時候會修改函式值,會使得其他只需要取狀態值得呼叫者關心更多的東西,從而產生副作用

5. 令函式攜帶引數        Parameterize Method
    * 概述:建立單一函式,以引數表達那些不同的值。
    * 動機:多個函式做著類似的工作,只是因為少數幾引數,或者引數個數不同。則可以使用一個單一函式將他們統一起來,為這個函式增加攜帶引數

6. 以明確函式取代引數    Replace Parameter with Explicit Methods
    * 概述:針對該引數的每一個可能值,建立一個獨立函式
    * 動機:有一個函式,取決於不同的引數值而採取不同的行為,這時可以為每個獨立之建立一個獨立函式。 某種程度與上一條相反

7. 保持物件完整        Preserve Whole Object
    * 概述:改為傳遞整個物件
    * 動機:從某個物件中取出若干值,並將他們作為某一次函式呼叫引數的時候,可以將引數修改為傳遞整個物件,以減少引數列表和對抗未來改動的風險。

8. 以函式取代引數     Replace Parameter with Methods
    * 概述:讓引數接受者去除該項引數,並直接呼叫前一個函式
    * 動機:如果函式可以通過其他途徑取得引數值,那麼久不應該通過引數值獲取。這樣可以減少引數列表,從而減少區域性變數。

9. 引入引數物件        Introduce Parameter Object
    * 概述:以一個物件取代這些引數
    * 動機:某些引數總是很自然的同時出現,當一組引數總是被同時傳遞給多個函式時候,則將這些引數整合成一個物件

10. 移除設定函式        Remove Setting Method
    * 概述:去掉欄位中的所有設定函式
    * 動機:物件中的某個欄位只應該在物件建立的時候被設定,然後就不再改變,則不因該提供設值函式,否則可能導致其值被修改,且容易混淆

11. 以工廠函式取代建構函式        Replace Constructor with Factory Method
    * 概述:將建構函式替換為工廠函式
    * 動機:通過靜態工廠函式來生產物件,從而將物件生產權利把控在本類中,呼叫者完成不需要關係這個新物件的構建過程

12. 封裝向下轉形        Encapsulate Downcast
    * 概述:將向下轉形動作移到函式中
    * 動機:某個函式返回的物件需要呼叫者自己強轉型別。這是可以將強轉型別封裝在函式內部,使得呼叫者無需關心實際型別。需要該函式返回型別有限且確定。

13. 以異常取代錯誤碼    Replace Error Code with Exception
    * 概述:將錯誤碼改用異常
    * 動機:丟擲異常能夠更加清楚知道程式碼執行過程產生的問題。

14. 以測試取代異常    Replace Exception with Test
    * 概述:修改呼叫者,使它在呼叫函式之前先做檢查
    * 動機:異常只應該用於哪些產生意料之外的錯誤行為,而不應該成為條件檢查的替代品。可以提供一個可重複執行的測試函式,讓呼叫者者呼叫前先檢查某個條件,在測試函式中處理try catch

ps: 從上述方法可以看出,其最終都是為了減少函式間傳參的複雜度,從而減少了區域性變數個數,也就提高的程式碼清晰度。

第六章、處理概括關係
1. 欄位上移    Pull Up Field
    * 概述:將欄位移到超類
    * 動機:兩個子類擁有相同的欄位

2. 函式上移    Pull Up Method
    * 概述:將函式移動至超類
    * 動機:有些函式各個子類中產生完全相同的結果

3. 建構函式本體上移    Pull Up Constructor Body
    * 概述:在超類中新建一個建構函式,並在子類中呼叫她
    * 動機:各個子類中擁有本體幾乎一致的建構函式

4. 欄位下移    Push Down Field
    * 概述:將這個欄位移動到需要她的類中
    * 動機:超類中某個欄位只被部分子類用到,讓資料與操作在同一個類中。

5. 提煉子類    Extract Subclass
    * 概述:新建一個子類,將只被某些例項用到特性移動該子類中
    * 動機:子類劃分不夠具體,不夠細緻,導致某個類包含過多本不該自己管理的東西,這是可以提煉一個新的子類。

6. 提煉超類    Extract Superclass
    * 概述:為兩個類建立一個超類,將相同的特性移至超類
    * 動機:兩個類具有相似的特性

7. 提煉介面    Extract Interface
    * 概述:將相同的子集提煉到一個獨立介面中
    * 動機:1. 若干客戶端使用類介面中的同一子集;2. 兩個類的介面有部分相同。這兩種情況都可以抽出一套共有介面。

8. 摺疊繼承體系    Collapse Hierachy
    * 概述:將繼承體系合為一體,以消除繼承體系
    * 動機:超類和子類,並沒有太大的區別。        ps: 

9. 塑造模板函式    Form Template Method
    * 概述:模板模式,不再概述

10. 以委託取代繼承    Replace Inheritance with Delegation
    * 概述:裝飾模式,不再概述
    * 動機:沒有繼承關係的兩個類使用繼承,不僅沒有程式碼複用,還繼承了父類大堆不相干方法,容易造成混淆;對於抽象的方法還必須覆寫。而裝飾模式可以自主選擇需要複用的方法。ps:非繼承關係的擴充套件請使用裝飾模式;有繼承關係的擴充套件請使用繼承

11. 以繼承取代委託    Replace Delegation with Inheritance
    * 概述:對於有繼承體系的仍然使用繼承體系,這樣可以複用父類的方法和欄位。

第七章、大型重構
1. 梳理並分解繼承體系    Tease Apart Inheritance
    * 概述:建立兩個繼承體系,並通過委託關係讓其中一個可以呼叫另一個
    * 動機:某個繼承體系同時承擔兩項責任,將此繼承體系拆解,使得抽象的分類更清楚、細緻從而提高類的複用率,並使程式碼更簡潔。

2. 將過程設計轉化為物件設計        Convert Procedural Design to Objects
    * 概述:將資料記錄變成物件,將大塊的行為分成小塊,並將行為移入相關的物件中
    * 動機:將面向過程的程式碼重構為面向物件的風格。

3. 將領域和表述/顯示分離    Separate Domain from Presentation
    * 概述:將領域邏輯分離出來,為他們建立獨立的領域類
    * 動機:從GUI中抽離領域邏輯,從而做到與顯示的分離。如MVC模式

4. 提煉繼承體系    Extract Hierachy
    * 概述:建立繼承體系,以一個子類表示一種特殊情況
    * 動機:有一個類做了太多的工作,其中一部分是大量的條件表示式完成的。


ps: 大型重構需要較高的對業務場景的抽象能力,使得分類更精確細緻,從而提高程式碼的利用率,使得編碼更簡單,結構更加清楚。
這樣一個細緻的體系:
1. 具有很強的對抗變化能力,她將未來所有可能變化的風險,限定在一個子類,甚至一個函式當中;
2. 具有很強的擴充套件能力,只需要通過實現不同的子類來增加新的功能,既不影響原有結構,更不影響其清晰度。