1. 程式人生 > >[讀書筆記]重構改善既有程式碼的設計

[讀書筆記]重構改善既有程式碼的設計

章節一 重構,第一個案例

快速而隨性(quick and dirty)地設計一個簡單的程式並沒有錯,但是,如果這是複雜系統中具有代表性的一段。。。

tip: 如果你發現自己需要為程式新增一個特性,而程式碼結構是你無法很方便地那麼做,那就先重構那個程式,使特性的新增比較容易進行,然後再新增特性

重構的第一步永遠相同:為即將修改的程式碼建立一組可靠的測試環境。

tip: 重構技術以微小的步伐修改程式。如果你犯下錯誤,很容易就可發現他。

tip: 任何一個傻瓜都能寫出計算機理解的程式碼。唯有寫出人類理解的程式碼,才是優秀的程式設計師。

在另一個物件屬性的基礎上使用switch-case語句,並不是什麼好主意,如果不得不使用,也是在物件自己的資料上使用,而不是在別人的資料上使用。

就Java語言體系來說,GOF是java基礎知識和j2EE知識之間一座隱形的橋。雖然它是隱性的,卻是不可越過(缺少)的。整個設計模式貫穿一個原理:面對介面程式設計,而不是面對實現。

章節二 重構原則

兩頂帽子比喻新增新功能和重構,軟體開發過程中,你可能會發現自己經常變換帽子,無論如何你都應該清楚自己戴的是哪一頂帽子。

『我不是個偉大的程式設計師:我只是個有著一些優秀習慣的好程式設計師而已。 —— Kent Beck』

幾乎任何情況下我都反對專門撥出時間進行重構。在我看來,重構本來就不是一件‘特別撥出時間做’的事情,重構應該隨時隨地進行。你不應該為重構而重構,你之所以重構,是因為你想做別的什麼事,而重構可以幫助你把那些事做好。

何時重構

  • 三次法則
  • 新增功能時一併重構
  • 修補錯誤時一併重構
  • 複審程式碼時一併重構

tip: 事不過三,三則重構。

電腦科學是這樣一門學科:它相信所有問題都可以通過多一個間接層來解決。

tip: 不要過早釋出介面。請修改你的程式碼擁有權政策,使重構更順暢。

關於效能,一件很有趣的事情是:如果你對大多數程式進行分析,你會發現它把大半時間都耗費在一小半程式碼上。如果你一視同仁地優化所有程式碼90%的優化工作都是白費勁兒,因為被你優化的程式碼有許多難得被執行起來。

效能熱點[hot spot]

章節三 程式碼的壞味道

Duplicated Code重複程式碼:同一個類中,兩個互為兄弟的子類中,毫不相干的類中。

Long Method 過長函式:你應該更積極進取地分解函式。

我們遵循這樣一條原則:每當感覺需要以註釋來說明點什麼的時候,我們就把需要說明的東西,寫進一個獨立的函式中,並以其用途(而非實現手法)命名。我們可以對一組或者短短一行程式碼做這件事。哪怕替換後的函式呼叫動作比函式自身還長,只要函式名稱能夠解釋其用途,我們也該毫不猶豫地這麼做。關鍵不在於函式的長度,而在於函式『做什麼』和『如何做』之間的語義距離。

如何確定該提煉哪一段程式碼呢?一個很好的技巧是:尋找註釋。它們通常是指出『程式碼用途和實現手法間的語義距離』的訊號。如果程式碼前方有一行註釋,就是在提醒你:可以將這段程式碼替換成一個函式,而且可以在註釋的基礎上給這個函式命名。就算只有一行程式碼,如果它需要以註釋來說明,那也值得將它提煉到獨立函式去。

條件和迴圈常常也是提煉的訊號。你可以使用分解條件表示式處理條件式。至於迴圈,你應該將迴圈和其內的程式碼提煉到一個獨立函式中。

Large Class過大類

如果單一類做太多事情,會出現大量的例項變數,可以使用Extract Class將數個變數一起提煉至新類中。提煉時應選擇類內彼此相關的變數。

一個類如果擁有太多程式碼,往往也適合使用Extract Class, Extract SubClass。這裡有個有用的技巧:先確定客戶端如何使用他們,然後運用Extract Interface為每一種使用方式提煉出一個介面。

Long Parameter List過長引數

抽取出一個引數物件。

Divergent Change發散式變化:一個類受到多個變化影響

一旦需要修改,我們希望能夠跳到系統的某一點,只在該處做修改。如果不能夠做到這一點,你就嗅出兩種緊密相關的刺鼻味道中的一種了。

如果某個類經常因為不同的原因在不同的方向上發生變化,發散式變化就出現了。把這個物件分成幾個,這樣每個物件就可以只因一種變化而需要修改,針對某一外界變化的所有相應修改,都只應該發生在單一類中,而這個新類中所有內容,都應該反應該外界變化。

Shotgun Surgery散彈式修改:一個變化影響多個類

如果遇到某種變化,必須在許多不同的類內做出小修改以響應,這個壞味道就是散彈式修改,和發散式變化正好相反。此時,應該使用Move method, Move fild 把所有需要修改的程式碼放進同一個類,

Feature Envy 依戀情節

函式對某個類的興趣,高過對自己宿主類的興趣,那就把函式移至另一個地點。有時候函式有一部分有依戀情節,那就先Extract Method 然後移動到另一個地點。

有些設計模式(策略模式和訪問者模式)破壞了這個規則。最根本的原則:將總是一起變化的東西放在一塊兒。

Data Clumps資料泥團

類中的值域,多個函式中的相同引數,這些總是綁在一起出現的資料應該放進屬於他們自己的物件中。

一個好的評斷方法是:刪掉眾多資料中的一筆。其他資料有沒有因而失去意義?如果它們不再有意義,這就是個明確的訊號:你應該為他們產生一個新物件。

Primitive Obsession基本型別偏執

物件的一個極具價值的東西是:他們模糊(甚至打破)了橫亙在基本資料和體積較大的類之間的界限。你可以輕鬆地編寫出一些與語言內建型別無異的小型類。

Switch Statementsswitch 驚悚現身

面相物件程式的一個最明顯特徵就是:少用switch-case語句。大多數時候,一看到switch語句,就應該考慮以多型來替換他。問題是多型該出現在哪兒?switch語句常常根據型別碼進行選擇,你要的是“與該型別碼相關的函式或類”,所以應該使用ExtractMethod將switch語句提煉到一個獨立函式中,再以MoveMethod將他搬移到需要多型性的那個類裡。

如果只是單一函式有一些選擇事例,且不想改動它們,那麼多型就有點殺雞用牛刀了。

Parallel Inheritance Hierarchies平行繼承體系

這是散彈式修改的一個特殊情況,此時每當你為某個類增加一個子類,必須也為另一個類相應增加一個子類。

應對策略是:讓一個繼承體系的例項引用另一個繼承體系的例項。

Lazy Class冗贅類:無用的類

Speculative Generality誇誇其談未來性:為未來功能預設的伏筆的類,會造成系統難以理解和維護

Temporary Field令人迷惑的暫時欄位

某個例項變數僅為某個特定情形而設。這樣的程式碼讓人不易理解,因為通常認為物件在所有時候需要它的所有變數 。使用Extract Class 把這個變數和相關函式提煉到一個獨立的類中。

Message Chains過渡耦合的訊息鏈

此時應該使用Hide Delegate 。可以在訊息鏈的不同位置進行這種重構。先觀察訊息鏈最終得到的物件是用來幹什麼的,看看能否以Extract Method把使用該物件的程式碼提煉到一個獨立函式中,在運用Move Method把這個函式推入訊息鏈。

Middle Man 中間轉手人

如果你看到某個類有一半的函式都委託給其他類,這樣就是過渡運用委託了。這時應該使用Remove Middle Man,直接和真正負責的物件打交道。如果這樣的(使用委託的)函式比較少,使用InlineMethod把他們放入呼叫端。如果這個MiddleMan還有其他行為,可以使用Replace Delegation with Inheritance,把它們變成實責物件的子類,這樣既可以擴充套件,又不必負擔那麼多的委託。

Inappropriate Intimacy 狎暱關係

有時候兩個類過於親密,話費太多時間去探究彼此的Private成分。繼承往往造成過度親密,子類對超類的瞭解總是超過後者的主觀願望。使用Replace Inheritance with Delegation

Alertnative Classes with Different Interfaces 異曲同工的類

如果函式簽名不同,做著同一件事,請運用Rename Method根據用途重新命名。這往往不夠,請反覆使用Move Method甚至Extract SuperClass

Incomplete Library Class 不完美的類庫

如果只想修改庫類的一兩個函式,可以運用Introduce Foreign Method,如果想新增一大堆額外的行為,就得運用Introduce Local Extension

Data Class幼稚的資料類

DataClass是指擁有一些欄位,以及用於訪問(讀寫)這些欄位的函式,除此之外一無長物。只是不會說話的資料容器。

對於public 欄位,使用Encapsulate Field將它們封裝起來。

對於容器類欄位,使用Encapsulate Collection 把他們封裝起來。

對於不該被別的類修改的欄位,使用Remove Setting Method

找出這些取值/設值函式被其他類運用的地點,嘗試以Move Method把那些呼叫行為搬移到Data Class

Data Class 必須承擔一定的責任。

Refused Bequest 被拒絕的饋贈

子類應該繼承超類的函式和資料,如果不想或不需要,這就意味著繼承體系錯誤。需要為這個子類新建一個兄弟類,在把用不到的欄位,方法都放入這個兄弟類中。

Comments 過多的註釋

如果需要註釋解釋一塊程式碼做了什麼,試試Extract Method;

如果函式已經提煉出來了,但還是需要註釋來解釋其行為,試試Rename Method;

如果需要註釋說明某些系統的需求規格,試試Introduce Assertion

tips: 當你感覺需要撰寫註釋時,請先嚐試重構,試著讓所有註釋都變得多餘。

章節四 構築測試體系

如果你想要重構,首要前提就是擁有一個可靠的測試環境。

自我測試類:每個類都應該有一個測試函式,並以他來測試這個類。

tip: 確保所有測試都完全自動化,讓它們檢查自己的測試結果。

tip: 一整組測試就是一個強大的bug偵測器,能夠大大縮減查詢bug所需要的時間。

tip: 每當接收到bug report ,請先撰寫一個單元測試來揭發這隻臭蟲。

觀察類該做的所有事情,然後針對任何一項功能的任何一種可能失敗情況,進行測試。

測試應該是一種風險驅動行為,而不是編寫大量測試,比如測試所以public函式。

tip: 編寫未臻完善的測試並實際執行,好過對完美測試的無盡等待。

tip: 考慮可能出錯的邊界條件,把測試火力集中在那。

tip: 當事情被大家認為應該會出錯時,別忘了檢查彼時是否有異常如預期般被丟擲。

tip: 不要因為測試無法捕捉臭蟲,就不撰寫測試程式碼,因為測試的確可以捕捉到大多數臭蟲。

章節五 重構列表

真要表示貨幣金額,我會使用Quantity模式。

重構的基本技巧:小步前進,頻繁測試。

章節六 重新組織函式

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 :以函式物件取代函式。你有一個大型函式,其中對區域性變數的使用使你無法採用Extract Method 。將這個函式放入一個單獨物件中,如此一來區域性變數就變成了物件內的欄位。然後你可以在同一個物件中,將這個大型函式分解為多個小函式。

Substitute Algorithm :替換演算法。你想要把某個演算法替換為另一個更清晰的演算法。將函式本體替換為另一個演算法。

章節七 在物件之間搬移特性

Move Method :搬移函式。你的程式中,有個函式與其所駐類之外的另一個類進行更多交流:呼叫後者或被後者呼叫。在該函式最常引用的類中建立一個有著類似行為的新函式。將舊函式變成一個單純的委託函式,或是將舊函式完全移除。

Move Field : 搬移欄位。你的程式中,有個欄位被其所駐類之外的另一個類更多地用到。在目標類新建一個欄位,修改原欄位的所有使用者,令他們改用新欄位。

Extract Class :提煉類。某個類做了應該由兩個類做的事。建立一個新類,將相關的欄位和函式從舊類搬移到新類。

一個類應該是一個清楚的抽象,處理一些明確的責任。

Inline Class :將類內聯化。某個類沒有做太多事情,將這個類的所有特性搬移到另一個類中,然後移除原類。

Hide Delegate : 隱藏委託關係。客戶通過一個委託類來呼叫另一個物件。在服務類上建立客戶所需的所有函式,用以隱藏委託關係。

Remove Middle Men : 移除中間人。某個類做了過多的簡單委託工作。讓客戶直接呼叫受委託類。

Introduce Foregin Method :引入外加函式。你需要為提供服務的類增加一個函式,但你無法修改這個類。在客戶類中建立一個函式,並以第一引數的形式傳入一個服務類例項。

Introduce Local Extension :引入本地擴充套件。你需要為服務類提供一下額外函式,但你無法修改這個類。建立一個新類,使它包含這些額外函式。讓這個擴充套件品成為源類的子類或包裝類。

章節八 重新組織資料

Self Encapsulate Filed :自封裝欄位。你直接訪問一個欄位,但與欄位之間的耦合關係逐漸變得笨拙。為這個欄位設立getter/setter函式,並且只以這些函式來訪問欄位。

我比較喜歡直接訪問方式,直到這種方式給我帶來麻煩為止。比如想訪問超類中的欄位,卻又想在子類中對這個變數的訪問改為一個計算後的值,此時是最該使用Self Encapsulate Field的時候。

Replace Data Value with Object :以物件取代資料值。你有一個數據項,需要和其他資料和行為一起使用才有意義。將資料項變為物件。

注意這樣一條規則:值物件應該是不可修改內容的。

Change Value to Reference :將值物件改為引用物件。你從一個類衍生出許多彼此相等的例項,希望將他們替換為同一個物件。將這個值物件變為引用物件。

Change Reference to Value :將引用物件改為值物件。你有一個引用物件,很小且不可變,而且不易管理。將它變成一個值物件。

Replace Array with Object :以物件取代陣列。你有一個數組,其中的元素各自代表不同的東西。以物件替換陣列,對於陣列中的每一個元素,以一個欄位來表示。

Dulplicate Observed Data :複製“被監視資料”。你有一些領域資料置身於GUI控制元件中,而領域函式需要訪問這些資料。將該資料複製到一個領域物件中。建立一個Observer模式,用以同步領域物件和GUI物件內的重複資料。

Change Unidirectional Association to Bidirectional :將單向關聯改為雙向關聯。兩個類都需要使用對方的特性,但其間只有一條單向連線。新增一個反向指標,並使修改函式能夠同時更新兩條連線。

Change Bidirectional Association to Unidirectional :將雙向關聯改為單向關聯。兩個類之間有雙向關聯,但其中一個類如今不再需要另一個類的特性。去除不必要的關聯。

Replace Magic Number with Symbolic Constant :以字面常量取代魔法數。你有一個字面數值,含有特別含義。創造一個變數,根據其意義為它命名,並將上述的字面數值替換為這個常量。

Encapsulate Field :封裝欄位。你的類中存在一個public的欄位。將它宣告為private,並宣告訪問函式。

Encapsulate Collection :封裝集合。有個函式返回一個集合。讓這個函式返回該集合的一個只讀副本,並在這個類中提供新增/移除集合元素的函式。

public Set getCourse() {
    return Collections.unmodifiableSet(mCourses);
}

Replace Record with Data Class :以資料類取代記錄。你需要面對傳統程式設計環境中的記錄結構。為該記錄建立一個“啞”資料物件。

Replace Typecode with Class : 以類取代型別碼。類之中有一個數值型別碼,但它並不影響類的行為。以一個新的類替換該數值型別碼。

Replace Typecode with Subclass : 以子類取代型別碼。你有一個不可變的型別碼,它會影響類的行為。以子類取代這個型別碼。

如果型別碼不會影響宿主類的行為,使用Replace Typecode with Class。如果影響宿主類的行為,最好的辦法是藉助多型來處理。一般來說這種情況的標誌是switch-caseif-then-else。應該使用Replace Conditional with Porlymorphism進行重構。在這之前,首先應該將型別碼替換為可擁有多型行為的繼承體系,以型別碼的宿主類為基類,並針對每一種型別碼各建一個子類。

有兩種特例:(1)型別碼值在物件建立之後發生了改變;(2)型別碼宿主類有了子類。這時候應該使用Replace Typecode with State/Strategy

Replace Typecode with State/Strategy :以State/Strategy取代型別碼。你有一個型別碼,它會影響類的行為,但是你無法通過繼承手法消除它。以狀態物件取代型別碼。

Replace Subclass with Fields :以欄位取代子類。你的各個子類的唯一差別,只在“返回常量資料”的函式身上。修改這些函式,使他們返回超類中的每個新增欄位,然後銷燬子類。

章節九 簡化條件表示式

Decompose Conditional :分解條件表示式。你有一個複雜的條件語句,從if then else三個段落中分別提煉出獨立函式。

程式之中,複雜的條件邏輯是最常導致邏輯複雜度上升的地點之一。

Consolidate Conditional Expression :合併條件表示式。你有一系列條件測試,都得到相同結果。將這些測試合併成一個條件表示式,並將這個表示式提煉成一個獨立函式。

Consolidate Duplicate Conditional Fragments :合併重複的條件片段。在條件表示式的每個分支上有著相同的一段程式碼。將這段重複程式碼搬移到條件表示式之外。

Remove Control Flag :移除控制標記。在一系列布林表示式中,某個變數帶有“控制標記(control flag)”的作用。以break語句或return語句取代控制標記。

set done to false
    while not done
    if (condition) {
    	do something
    	set done to true
    }
    next step of loop

Replace Nested Conditional with Guard Clauses :以衛語句取代巢狀條件表示式。函式中的條件邏輯使人難以看清正常的執行路徑。使用衛語句表現所有特殊情況。

衛語句就是把複雜的條件表示式拆分成多個條件表示式,比如一個很複雜的表示式,嵌套了好幾層的if - then - else 語句,轉換為多個if語句,實現它的邏輯,這多條的if 語句就是衛語句。

Replace Conditional with Polymorphism :以多型取代條件表示式。你手上有多個條件表示式,它根據物件型別的不同而選擇不同的行為。將這個條件表示式的每個分支放進一個子類內的覆寫函式中,然後將原始函式宣告為抽象函式。

Introduce Null Object :引入Null物件。你需要再三檢查物件是否為null。將null值替換為null物件。

Introduce Assertion :引入斷言。某一段程式碼需要對程式狀態做出某種假設。以斷言明確表現這種假設。

常常有這樣一段程式碼:只有當某個條件為真時,該段程式碼才能正常執行。斷言是一個條件表示式,應該總是為真。如果它失敗,表示程式設計師犯了錯誤。

實際上,程式的成品往往將斷言統統刪除。

你可以新建一個Assert類,用於處理各種情況下的斷言。

章節十 簡化函式呼叫

Rename Method :函式改名。函式的名稱未能揭示函式的用途。修改函式名稱。

函式的名稱應該準確表達它的用途。給函式命名有一個好辦法:首先考慮給這個函式寫上一句怎樣的註釋,然後想辦法將註釋變成函式名稱。

如果你看到一個函式名稱不能很好地表達它的用途,應該馬上加以修改。

要想成為真正的程式設計高手,起名水平至關重要。(嚯 ~!)

Add Parameter :新增引數。某個函式需要從呼叫端得到更多資訊。為此函式新增一個物件引數,讓該物件帶進函式所需資訊。

Remove Parameter : 移除引數。函式本體不再需要某個引數。將該引數去除。

Separate Query from Modifier :將查詢函式和修改函式分離。某個函式即返回物件狀態值,又修改物件狀態。建立兩個不同的函式,其中一個負責查詢,另一個負責修改。

任何有返回值的函式,都不應該有看得到的副作用。

Parameterize Method :令函式攜帶引數。若干函式做了類似工作,但在函式本題中卻包含了不同的值。建立單一函式,以引數表達那些不同的值。

Replace Parameter with Explicit Method :以明確函式取代引數。你有一個函式,其中完全取決於引數值而採取不同行為。針對該引數值的每一個可能值,建立一個獨立函式。

Preserve Whole Object :保持物件完整。你從某個物件中取出若干值,將它們作為某一次函式呼叫時的引數。改為傳遞整個物件。

Replace Parameter with Methods :以函式取代引數。物件呼叫某個函式,並將所得結果作為引數,傳遞給另一個函式。而接受該引數的函式本身也能夠呼叫前一個函式。讓引數接受者去除該項引數,並直接呼叫前一個函式。

你應該只在必要關頭才新增引數,預先新增這個引數很可能並不是你所需要的。

Introduce Parameter Object : 引入引數物件。某些引數總是很自然地同時出現。以一個物件取代這些引數。

你常會看到特定的一組引數總是一起被傳遞。這就是資料泥團(Data Clumps)。可以運用一個物件包裝這些資料,再以物件取代他們。

本項重構的價值在於縮短引數列。

儘量以“範圍物件”取代用一對值表示一個範圍的程式碼。

Remove Setting Method:移除設值函式。類中的某個欄位應該在物件建立時被設值,然後就不再改變。去掉該欄位的所有設值函式,同時將該欄位設為final.

Hide Method :有一個函式,從來沒有被其他任何類用到。將這個函式改為private。

重構往往促使你修改函式的可見度。

Replace Constructor with Factory Method :以工廠函式取代建構函式。你希望在建立物件時不僅僅是做簡單的構建動作。將建構函式替換為工廠函式。

Encapsulate Downcast :抽取向下轉型。某個函式返回的物件,需要由函式呼叫者執行向下轉型。將向下轉型動作移到函式中。

Replace Error Code with Exception :以異常取代錯誤碼。某個函式返回一個特定的程式碼,用以表示某種錯誤情況。改用異常。

程式碼的可理解性是我們虔誠追求的目標。(內心OS:不不不不~~~能跑就行)

Replace Exception with Test :以測試取代異常。面對一個呼叫者可以預先檢查的條件,你丟擲了一個異常。修改呼叫者,使它在呼叫函式之前先做檢查。

“異常”只應該被用於異常的,罕見的行為,也就是那些產生意料之外的錯誤的行為,而不應該稱為條件檢查的替代品。

章節十一 處理概括關係

概括關係 - generalization - 即繼承關係,主要是將函式上下移動於繼承體系之中。

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 Object :將過程化設計轉化為物件設計。你手上有一些傳統過程化風格的程式碼。將資料記錄變為物件,將大塊的行為變為小塊,並將行為移入相關物件之中。

Seprate Domain from Presentation :將領域和表述/顯示分離。某些GUI類之中包含了領域邏輯。將領域邏輯分離出來,為它們建立獨立的領域類。

MVC模式最核心的價值在於:它將使用者介面程式碼(檢視、展示層)和領域邏輯(模型)分離。

Extract Hierarchy : 提煉繼承體系。你有某個類做了太多工作(瑞士軍刀般的類),其中一部分工作是以大量條件表示式完成的。建立繼承體系,以一個子類表示一種特殊