[讀書筆記]重構改善既有程式碼的設計
章節一 重構,第一個案例
快速而隨性(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 Statements
switch 驚悚現身
面相物件程式的一個最明顯特徵就是:少用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-case
,if-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
: 提煉繼承體系。你有某個類做了太多工作(瑞士軍刀般的類),其中一部分工作是以大量條件表示式完成的。建立繼承體系,以一個子類表示一種特殊