《重構》的讀書筆記–方法列表
- 6.1 (P110)Extract Method(提煉函數)
- 6.2 Inline Method(內聯函數)117
- 6.3 Inline Temp(內聯臨時變量)119
- 6.4 Replace Temp with Query(以查詢取代臨時變量)120
- 6.5 Introduce Explaining Variable(引入解釋性變量)124
- 6.6 Split Temporary Variable(分解臨時變量)128
- 6.7 Remove Assignments to Parameters(移除對參數的賦值)131
- 6.8 Replace Method with Method Object(以函數對象取代函數)135
- 6.9 Substitute Algorithm(替換算法)139
- 第7章 在對象之間搬移特性141
- 7.1 Move Method(搬移函數)142
- 7.2 Move Field(搬移字段)146
- 7.3 Extract Class(提煉類)149
- 7.4 Inline Class(將類內聯化)154
- 7.5 Hide Delegate(隱藏“委托關系”)157
- 7.6 Remove Middle Man(移除中間人)160
- 7.7 Introduce Foreign Method(引入外加函數)162
- 7.8 Introduce Local Extension(引入本地擴展)164
- 8.1 Self Encapsulate Field(自封裝字段)171
- 8.2 Replace Data Value with Object(以對象取代數據值)175
- 8.3 Change Value to Reference(將值對象改為引用對象)179
- 8.4 Change Reference to Value(將引用對象改為值對象)183
- 8.5 Replace Array with Object(以對象取代數組)186
- 8.6 Duplicate Observed Data(復制“被監視數據”)189
- 8.7 Change Unidirectional Association to Bidirectional(將單向關聯改為雙向關聯)197
- 8.8 Change Bidirectional Association to Unidirectional(將雙向關聯改為單向關聯)200
- 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法數)204
- 8.10 Encapsulate Field(封裝字段)206
- 8.11 Encapsulate Collection(封裝集合)208
- 8.12 Replace Record with Data Class(以數據類取代記錄)217
- Type Code(類型碼)
- 8.13 Replace Type Code with Class(以類取代類型碼)218
- 8.14 Replace Type Code with Subclasses(以子類取代類型碼)223
- 8.15 Replace Type Code with State/Strategy(以State/Strategy取代類型碼)227
- 8.16 Replace Subclass with Fields(以字段取代子類)232
- 9.1 Decompose Conditional(分解條件表達式)238
- 9.2 Consolidate Conditional Expression(合并條件表達式)240
- 9.3 Consolidate Duplicate Conditional Fragments(合并重復的條件片段)243
- 9.4 Remove Control Flag(移除控制標記)245
- 9.5 Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)250
- 9.6 Replace Conditional with Polymorphism(以多態取代條件表達式)255
- 9.7 Introduce Null Object(引入Null對象)260
- 9.8 Introduce Assertion(引入斷言)267
- 10.1 Rename Method(函數改名)273
- 10.2 Add Parameter(添加參數)275
- 10.3 Remove Parameter(移除參數)277
- 10.4 Separate Query from Modifier(將查詢函數和修改函數分離)279
- 10.5 Parameterize Method(令函數攜帶參數)283
- 10.6 Replace Parameter with Explicit Methods(以明確函數取代參數)285
- 10.7 Preserve Whole Object(保持對象完整)288
- 10.8 Replace Parameter with Methods(以函數取代參數)292
- 10.9 Introduce Parameter Object(引入參數對象)295
- 10.10 Remove Setting Method(移除設值函數)300
- 10.11 Hide Method(隱藏函數)303
- 10.12 Replace Constructor with Factory Method(以工廠函數取代構造函數)304
- 10.13 Encapsulate Downcast(封裝向下轉型)308
- 10.14 Replace Error Code with Exception(以異常取代錯誤碼)310
- 10.15 Replace Exception with Test(以測試取代異常)315
- 11.1 Pull Up Field(字段上移)320
- 11.2 Pull Up Method(函數上移)322
- 11.3 Pull Up Constructor Body(構造函數本體上移)325
- 11.4 Push Down Method(函數下移)328
- 11.5 Push Down Field(字段下移)329
- 11.6 Extract Subclass(提煉子類)330
- 11.7 Extract Superclass(提煉超類)336
- 11.8 Extract Interface(提煉接口)341
- 11.9 Collapse Hierarchy(折疊繼承體系)344
- 11.10 Form TemPlate Method(塑造模板函數)344
- 11.11 Replace inherited with Delegation(以委托取代繼承)352
- 11.12 Replace Delegation with Inherited(以繼承取代委托)352
第5章 重構列表
5.1 重構的記錄格式103
這個格式可以作為自己未來記錄重構手法的標準,方便查閱自己常用的和總結的重構方式:
- 名稱:重構詞匯表
- 概要:適用的情景,以及所做的事情
- 動機:說明“為什么需要”和“什么情況不做”
- 做法:重構的步驟(看似簡單,其實很重要,因為重構不建議跳躍完成,最好保證每次一小步都是正確的)
- 范例:通過例子正確的理解重構手法的操作方式
5.2 尋找引用點105
引用點:就是那些被重構的代碼被哪些代碼調用了。現在的重構工具已經可以簡化這些操作,但是工具永遠都是有限的,除了工具還應該熟悉一些基本的操作手段。
5.3 這些重構手法有多成熟106
學習和使用這些重構僅僅是個起點,業務在變、工具在變、語言在變、對事物的理解也在變,因此完全挺有自己的重構手段才是最終的目標。
第6章 重新組織函數
- Extract Method是本章的重點,用于解決Long Methods問題;
- Extract Method的困難是處理局部變量,特別是局部變量中的臨時變量,為此需要本章中的其他方法作為補充。
6.1 (P110)Extract Method(提煉函數)
動機:Long Methods問題或者代碼需要Comments問題;
方法:(可以使用IDE提供的重構工具,下面的具體操作的原理)
- 創造一個新函數,根據這個函數的意圖來對它命名;
- 將提煉的代碼拷貝到新建函數中;
- 檢查提煉的代碼是否引用了“作用域限于原函數”的變量(局部變量、原函數參數)
- 檢查提煉的代碼是否包含了“僅用于被提煉代碼段”的臨時變量
- 如果有,在目標函數中將之聲明為局部變量
- 檢查被提煉代碼段,看看是否有任何局部變量的值被他改變,
- 如果臨時變量的值被修改了,嘗試將提煉的代碼變為一個查詢,將結果返回給相關變量;
- 如果不好操作,或者被修改的變量多于一個,可以嘗試(分解臨時變量/以查詢替換變量)等手段了。
- 將被提煉代碼段中需要被讀取的局部變量,當參數傳給目標函數
- 處理完所有局部變量后,編譯,檢查
- 在源函數中,將被提煉的代碼替換為對目標函數的調用
- 編譯,檢查,測試
6.2 Inline Method(內聯函數)117
動機:當函數內部的代碼與其名稱一樣易懂,可以在函數調用點插入函數本體,移除該函數。
補充:對有一群不太合理的函數,可以先內聯為一個長函數,然后再提煉出合理的小函數
6.3 Inline Temp(內聯臨時變量)119
動機:當臨時變量只被一個簡單的表達式賦值一次,而且它妨礙其他重構方法時,才需要重構
條件:Inline Temp多半是為Replace Temp with Query(以查詢取代臨時變量)準備
方法:將所有對該變量的引用動作替代成對它賦值的表達式本身。
6.4 Replace Temp with Query(以查詢取代臨時變量)120
動機:你的程序以一個臨時變量保存一個表達式的計算結果
方法:將表達式提煉出獨立的函數,然后臨時變量的調用替換成新函數的調用。此后新函數也能被調用。
具體方法: 將提煉出來的函數用private修飾,如果獨立函數有副作用,那對它進行Separate Query from Modifier(將查詢函數和修改函數分離)
6.5 Introduce Explaining Variable(引入解釋性變量)124
將復雜表達式(或者其中一部分)的結果賦值給一個臨時變量,用臨時變量名稱來解釋表達式的用途
6.6 Split Temporary Variable(分解臨時變量)128
臨時變量被賦值超過一次,但是它既不是循環變量也不是被用于收集計算結果
原因:一個變量應該承擔一個責任,如果被賦值多次很可能承擔了多個責任
方法:針對每次賦值,創建新的臨時變量
6.7 Remove Assignments to Parameters(移除對參數的賦值)131
java是值傳遞,對參數的任何修改都不會對調用端產生影響,所以對于用過引用傳遞的人可能會發生理解錯誤
參數應該僅表示“被傳遞過來的東西”
6.8 Replace Method with Method Object(以函數對象取代函數)135
動機:在大型函數內,對局部變量的使用導致難以使用Extract Method(提煉函數)進行重構
方法:將這個函數放入一個對象里,局部變量變成對象成員變量,然后可以在同一對象中將這個大型函數分解為多個小型函數。
原因:局部變量會增加分解函數的困難度
6.9 Substitute Algorithm(替換算法)139
把某個算法替換成更清晰的方法(算法)。
第7章 在對象之間搬移特性141
本章的重點是搬移(Move)?!皼Q定把責任放在哪里”是面向對象設計中最重要的事情之一,但是剛開始設計時,由于技術和業務知識的不足無法保證做出的決定是正確的,那么后期調整中將“責任”搬移到正確的對象中就是重要的重構手段。而Move Method(142)和Move Field(146)就是搬移“責任”過程中最基本的兩個方法。
7.1 Move Method(搬移函數)142
動機:類中某個函數與其他類交互過多
方法:將該函數搬移到交互最多的類里面,將舊函數變成委托函數或者刪除。
具體方法:
- 檢查源類中被源函數使用的一切特性,如果特性被其他函數使用,考慮這些函數一起搬移
- 檢查源類的子類和超類,看看是否有該函數的聲明,如果出現,很可能不能搬移。
- 目標類需要使用源類的特性:
- 將該特性轉移到目標類;
- 建立目標類到源類之間引用。
- 將源類作為參數傳給目標類
- 將該特性作為參數傳給目標類
- 如果源函數包含異常處理,需要考慮是在目標類還是源函數處理
7.2 Move Field(搬移字段)146
動機:類中某個字段被其他類頻繁使用(包括:傳參數、調用取值函數、調用設值函數)
方法:將該字段搬移到目標類
具體方法:
- 先封裝這個字段;
- 在目標類建立這個字段,并且封裝;
- 設定目標對象;
- 替換對源字段的引用為目標類的取值函數
7.3 Extract Class(提煉類)149
動機:一個類做了兩個類的事
方法:
- 建立新類,將相應的字段和函數放到新類
- 使用Move Field重構;
- 使用Move Method重構;
- 判斷是否需要公開新類。
7.4 Inline Class(將類內聯化)154
動機:某個類功能太少,與Extract Class(提煉類)相反 方法:將這個類的所有特性搬移到另一類中,移除該類。
原因:多次Extract Class后,原類大部分功能被移走,將這個萎縮類與其他相近的類合并
7.5 Hide Delegate(隱藏“委托關系”)157
動機:客戶端通過委托類來取得另一個對象的信息
方法:在服務類上建立客戶端所需數據的函數,然后隱藏委托關系
依據:符合“封裝”的特性。當委托類發生變化不會對客戶端造成影響,減少客戶端與調用者之間的耦合性。
7.6 Remove Middle Man(移除中間人)160
動機:某個類做了過多的委托動作
方法:讓客戶端直接調用委托類,與Hide Delegate(隱藏“委托關系”)相反 依據:當原委托類的特性越來越多,服務類的委托函數將越來越長,需要讓客戶端直接調用,避免服務類淪為中間人。
7.7 Introduce Foreign Method(引入外加函數)162
動機:使用的類無法提供某個功能,但是又不能修改該類
方法:新建函數,并將服務類的對象實例作為參數傳入。
具體動機:如果需要為服務類增加大量的方法,請考慮使用Introduce Local Extension(引入本地擴展)
7.8 Introduce Local Extension(引入本地擴展)164
動機:使用的類無法提供多個功能,但是又不能修改該類
方法:建立新的類,在新類中建立需要的功能函數,可以作為服務類的子類實現新的類,也可以包裝服務類實現新的類。
具體情況:
- 首選子類,工作量最小
- 但是必須在對象創建期實施,如果不行就只能選擇包裝類;
- 子類的對象不能修改父類的數據,否則建議選擇包裝類,因為會導致父類對象與子類對象的數據可能不一致
- 包裝類需要實現被包裝對象的所有接口,工作量很大。
第8章 重新組織數據
本章重點是如何更好地封裝各種類型的數據。最常用的手段就是Self Encapsulate Field(171)
8.1 Self Encapsulate Field(自封裝字段)171
動機:直接訪問一個字段,但是字段之間的耦合關系逐漸變得笨拙。
方法:自封裝就是在對于類內部的字段也封裝一個設值取值的函數。
爭論:字段訪問方式是直接訪問還是間接訪問一致爭論不斷
間接訪問的好處:
- 子類可以通過覆蓋一個函數來改變獲取數據的途徑;
- 支持更靈活的數據管理,如延遲加載(需要用到才加載)等。直接訪問的好處:代碼容易讀懂,理解不需要轉換為取值函數。
8.2 Replace Data Value with Object(以對象取代數據值)175
動機:假如一個數據項需要與其他數據一起使用才有意義。數據已經不僅僅由一條簡單的數據項組成,例如:電話號碼
方法:將數據變成對象。
8.3 Change Value to Reference(將值對象改為引用對象)179
動機:一個類有許多相等的實例,希望把這些相等的實例統一為一個對象,方便統一修改或者進行相等性比較
方法:將值對象變成引用對象
“引用對象”與“值對象”的區別:
- 每個引用對象代表著現實中一個對象,使用對象的一致性用來檢測兩個對象是否相等,即(==)
- 值對象完全由其自身的值來相互區分,需要重寫一些方法用來檢測兩個對象是否相等。(重寫equals()和hashcode()方法)具體方法:
- 需要使用工廠模式來創建對象
- 需要另一個對象(或者是自身)作為訪問點來訪問定義的引用對象,對象用Dictionary或者HashTable來保存對象
- 決定對象是預先創建還是動態創建
8.4 Change Reference to Value(將引用對象改為值對象)183
動機:引用對象,很小且不可變,而且不易管理
- 很小:創建許多也不會消耗太多內存
- 不可變:不需要復雜的管理代碼,也不需要考慮同步問題,還會造成別名問題具體方法:
- 檢查重構目標是否是不可變對象或者可修改成不可變對象
- 使用Remove Setting Method變成不可變對象
- 如果無法修改成不可變對象,就放棄重構
- 重寫hashCode和equals()方法
- 取消使用的工廠模式,并將對象的構造函數設為public
8.5 Replace Array with Object(以對象取代數組)186
動機:如果數據存儲的值代表不同的東西。
方法:將數組變成對象,數組的每個元素用字段表示
8.6 Duplicate Observed Data(復制“被監視數據”)189
動機: 有業務數據置身于GUI控件中,而與業務相關的函數需要訪問這些數據
方法:將業務數據復制到業務類中。建立Observer模式,同步UI和業務類的數據。
8.7 Change Unidirectional Association to Bidirectional(將單向關聯改為雙向關聯)197
動機:兩個類相互之間都需要對方的數據,但是相互之間只有一條單向的連接
這個重構需要添加測試,因為“反向指針”很容易造成混亂。
具體方法:
- 在被引用類添加字段,保存引用類的指針;
- 判斷由哪個類來控制關聯關系;
- 如果兩者都是引用對象,且關聯關系為“一對多”的關系,那么就由“擁有單一引用”的對象作為控制者;
- 如果A對象是B對象的部件,則由B對象負責控制關系;
- 如果兩者都是引用對象,且關聯關系為“多對多”的關系,那么隨意確定一個對象作為控制者。
- 在被控端建立輔助函數,命名清晰地描述其用途;
- 如果修改函數在控制端,則由其負責更新反向指針;
- 如果修改函數在被控制端,則在控制端建立一個修改反射指針的函數,由修改函數調用其修改反向指針。
- 兩者是一對多關系,有單一引用承擔控制關聯關系責任
8.8 Change Bidirectional Association to Unidirectional(將雙向關聯改為單向關聯)200
動機:兩個類有雙向關聯,但是一個類不再需要另一個類的特性
原因:
- 雙向關聯可能造成僵尸對象,不能被清除釋放內存。
- 使兩個類存在耦合關系,一個類的變化會導致另一類的變化。方法:去除雙向關聯
困難:檢查可行性
8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法數)204
動機:有一個字面常量(除了0和1之外)
方法:創建常量賦值以該字面常量,給予命名。
8.10 Encapsulate Field(封裝字段)206
動機:一個類有public字段
將它聲明為private,并提供相應的訪問函數
8.11 Encapsulate Collection(封裝集合)208
動機:類中使用集合,但是集合不能提供給用戶直接操作,而是提供函數操作集合,降低用戶與集合之間的耦合度
方法:提供函數返回集合的只讀副本,并提供增加和刪除集合元素的函數
具體方法:
- Java2:封裝Set
- Java1.1:封裝Vector
- 封裝數組
8.12 Replace Record with Data Class(以數據類取代記錄)217
動機:面對舊程序中Record數據結構,新建類取代它
方法:為該記錄創建一個“啞”數據對象。
Type Code(類型碼)
常見于過去的C語言編程中,因為沒有枚舉,所以采用類型碼的方式標注。這個重構遇到的機會比較小
8.13 Replace Type Code with Class(以類取代類型碼)218
動機:類中的數值類型碼不影響類的行為
方法:以一個新類替代類型碼
8.14 Replace Type Code with Subclasses(以子類取代類型碼)223
動機:有一個不可變的類型碼且影響類的行為
標志:switch或者if-then-else類的條件表達式,這個重構是Replace Conditional with Polymorphism的準備工具
方法:以子類取代這個類型碼
8.15 Replace Type Code with State/Strategy(以State/Strategy取代類型碼)227
動機:有一個類型碼且影響類的行為,但是無法通過繼承消除(類型碼可變化)
方法:以狀態對象取代。
8.16 Replace Subclass with Fields(以字段取代子類)232
動機:各個子類唯一區別只在“返回常量的數據”的函數上
方法:修改這些函數使它們返回超類的某個(新增)字段,然后銷毀子類。
第9章 簡化條件表達式 237
條件邏輯非常復雜,也非常難以理解,通過重構將復雜的邏輯展現為簡單的邏輯塊。
有些重構方法看起來非常簡單,因為重構最重要的思想不是方法有多精妙,而是傳達了一個小步快走的理念。就是一次只完成一個小重構,然后測試確保沒有錯誤。然后,再進行下一個小重構和測試。從而整個大重構通過多個簡單的小重構完成,避免大重構出錯后需要全部回滾的問題。
9.1 Decompose Conditional(分解條件表達式)238
動機:if-then-else語句,不同分支做不同事動機成大型函數,本身就難以閱讀,尤其在帶有復雜條件的邏輯中。方法:
- 將if語句提煉為函數
- 將then和else段落提煉為函數
- 對于存在嵌套的條件邏輯,先判斷是否可以用Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)消除。不行再分解每個條件
9.2 Consolidate Conditional Expression(合并條件表達式)240
動機:有一系列條件判斷都服務于共同的目標
方法:將這些條件判斷合并為同一個表達式,再將這個表達式提煉為獨立函數
原因:
- 只是一次條件檢查,只是存在多個并列條件需要檢查而已
- 為Extract Method(提煉函數)做準備,通過函數名告知“為什么這么做”
9.3 Consolidate Duplicate Conditional Fragments(合并重復的條件片段)243
動機:在條件表達式的不同分支中存在相同的代碼
方法:將這些重復代碼搬移到條件表達式之外,多行代碼還可以再提煉為獨立函數。
例如:當try和catch執行相同代碼,可以將代碼移到final區段。
9.4 Remove Control Flag(移除控制標記)245
動機:在循環執行的程序段中,某個變量定義為判斷條件中的控制標記(control flag),增加了代碼理解的復雜度
方法:
- 以break或者continue代替;
- 也可以通過函數調用和return語句來實現。
9.5 Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件表達式)250
衛語句:如果某個條件極其罕見,就應該單獨檢查該條件,并在該條件為真時立刻從函數中返回,這樣的單獨檢查被稱為“衛語句”(guard clauses)
動機:函數中的條件邏輯使人難以看清正確的執行路徑。
方法:使用衛語句表現所有的特殊情況
9.6 Replace Conditional with Polymorphism(以多態取代條件表達式)255
動機:存在條件表達式根據對象的類型不同選擇不同的行為
方法:將表達式分支放進不同子類,然后重寫方法,將原始函數提煉為抽象函數。
9.7 Introduce Null Object(引入Null對象)260
動機:需要再三檢查對象是否為null
方法:將null值替代為null對象,如果原始類不允許修改可以使用Null接口來檢查“對象是否為Null”。
9.8 Introduce Assertion(引入斷言)267
動機:某段代碼需要對程序狀態顯式地表明某種假設
方法:以斷言明確表現這種假設
具體方法: 斷言在 發布的時候統統 被跳過
第10章 簡化函數調用
使接口變得更加簡潔易用的重構方法。
- 修改函數名稱,使之容易理解;
- 縮短參數列表;
- 不同的功能分離到不同的函數中;
- 隱藏函數,提升接口的質量。
10.1 Rename Method(函數改名)273
動機:函數的名稱不能說明函數的用途
方法:將舊函數代碼搬移到新函數,舊函數跳轉到新函數。
10.2 Add Parameter(添加參數)275
動機:被調用的函數需要從調用函數中得到更多的信息
方法:為被調用的函數添加參數
抉擇:
- 現有參數是否提供足夠的信息?
- 這個函數是否應該移動到擁有該信息的對象中?
- 加入新參數是否合適?
- 如果需要的參數過多,是否需要使用Introduce Parameter Object(引入參數對象)?
10.3 Remove Parameter(移除參數)277
動機:函數不需要某個參數(不需要了就放棄,保留也需要付出代價)
方法:
- 如果是獨立的函數,直接將該參數移除
- 如果是多態函數,不能移除,就增加一個新的沒有這個參數的函數,使調用者的工作得到簡化
10.4 Separate Query from Modifier(將查詢函數和修改函數分離)279
動機:某個函數既修改對象狀態,又返回對象狀態值。(使調用者擔心誤操作修改了不應該修改的數據,增加調用者的操作負擔)
本質:函數功能簡潔、明確,如果一個函數具備多個功能,就把它們分離成多個函數。
方法:建立兩個不同的函數,其中一個負責查詢,另一個負責修改。
原則:
- 任何一個有返回值的函數都不應該有看得到的副作用。
- 編碼中主要考慮的不是代碼的效率,而是代碼的易讀性,效率可以在未來上線的時候再根據實際需要調整。多線程:將修改和查詢函數封裝在一個同步函數中分開調用。
10.5 Parameterize Method(令函數攜帶參數)283
動機:幾個函數,做了類似的工作,只是代碼中的系數不同
方法:建立單一函數,以參數作為系數
10.6 Replace Parameter with Explicit Methods(以明確函數取代參數)285
動機:函數依賴于參數值的不同而采取不同的行為
方法:針對該參數的每個可能值,建立獨立函數。
對比:與Parameterize Method(令函數攜帶參數)相反,但是目的都是把復雜的邏輯判斷消除 目的:提供清晰的入口。
如果參數值對函數行為影響不大,不應該采用此方法。
10.7 Preserve Whole Object(保持對象完整)288
動機:從某個對象取若干個值,把他們作為參數傳給函數
方法:改為調用整個對象
目的:避免過長參數列表
缺陷:如果傳遞的是值,那么函數只依賴那些值;如果傳遞的是對象,函數則依賴對象,會導致耦合
注意:有時候函數使用了很多來自某個對象的數據,那么應該考慮使用(Move Method)將這個函數移到關系密切的對象中
10.8 Replace Parameter with Methods(以函數取代參數)292
動機:對象調用某個函數,并將所得結果作為參數傳遞給另一個函數,而接受該參數的函數本身也能夠調用前一個函數
方法:讓參數接受者去除該項參數,并直接調用前一個函數
10.9 Introduce Parameter Object(引入參數對象)295
動機:有些參數總是自然地同時出現
方法:用一個對象把這些參數包裝起來進行傳遞
目的:
- 縮短參數列表長度;
- 函數具有一致性,降低理解和修改代碼的難度
10.10 Remove Setting Method(移除設值函數)300
動機:類的某個字段應該對象創建的時候被設置,然后不再改變
方法:去掉該字段的設置函數
- 如果對參數的運算很簡單,而且只有一個構造函數,就可以直接在構造函數中初始化。
- 如果修改復雜,或者有多個函數試圖改變這個字段,那么就需要提供一個獨立函數,并給予獨立函數一個清楚表達用途的名字
- 如果是子類希望修改超類的字段
- 那么最好是使用超類的構造器實現改變;
- 或者通過擁有能夠清楚表達用途的名字的函數來實現。
- 如果修改集合字段,請使用Encapsulate Collection(208)實現。
10.11 Hide Method(隱藏函數)303
動機:有一個函數,從來沒有被任何類調用
方法:將該函數設為private
補充:函數可見度不夠,在編譯的時候就可以發現;而函數過見度過高,則需要通過一些工具(Lint)來輔助檢查。
10.12 Replace Constructor with Factory Method(以工廠函數取代構造函數)304
動機:創建對象時不僅僅是做簡單的構建動作方法:將構造函數替換為工廠模式范例:
- 根據整數(實際是類型碼)創建對象;
- 根據字符串創建子類對象;
- 以函數創建子類;
10.13 Encapsulate Downcast(封裝向下轉型)308
動機:某個函數返回的對象,需要由函數調用者執行向下轉型(downcast)
方法:將向下轉型移到函數中
10.14 Replace Error Code with Exception(以異常取代錯誤碼)310
動機:某個函數返回一個特定的代碼,表示某個錯誤的情況
方法:取消那個代碼判斷,改用拋出異常
范例:
- 非受控異常:使用守衛語句檢查這個異常情況;
- 受控異常:需要修改的調用者函數和被調用者函數,步驟太大,容易出錯??梢韵葎摻ㄒ粋€臨時的中間函數,保留原函數,使所有的調用都改為新函數后,刪除原函數,再修改新函數名稱,即可。
10.15 Replace Exception with Test(以測試取代異常)315
動機:本該由調用者自行檢查的條件,由被調用者拋出了一個可控異常。
方法:修改調用者,使它在調用函數之前做檢查。
補充:異常就應該放在可能發生異常的地方使用。即可以預測的,可以通過檢查避免的,那就是錯誤,不該發生;不能預測的,無法通過檢查避免的,那就是異常。例如:賬戶余額小于取錢數目,申請取錢這個就是錯誤;賬戶余額大于取錢數目,取不出錢來就是異常。
第11章 處理概括關系 319
概括關系(generalization,即繼承關系、泛化關系)
11.1 Pull Up Field(字段上移)320
動機:兩個子類擁有相同的字段
方法:
- 將該字段移動到超類,去除重復數據聲明;
- 將使用該字段的行為搬移到超類,去除關于這個字段的重復行為。
- 考慮對超類的該字段使用Self Encapsulate Field(171)
11.2 Pull Up Method(函數上移)322
動機:有些函數,在各個子類產生相同的結果。
方法:
- 將該函數移動到超類
- 如果被提升的函數引用了子類中的函數
- 如果可以將引用函數提升,就一起提升
- 如果不可以將引用函數提升,可以在超類里面那個抽象函數
11.3 Pull Up Constructor Body(構造函數本體上移)325
動機:你在各個子類擁有一些構造函數,它們的本地幾乎完全一致
方法:在超類新建一個構造函數,并在子類構造函數中調用它。
具體方法:
- 將共同代碼放在子類構造函數起始處,然后再復制到超類構造函數中。
- 將子類構造函數中共同代碼刪除,改用調用新建的超類構造函數。
11.4 Push Down Method(函數下移)328
動機:超類中的某個函數只與部分而非全部子類有關
方法:將這個函數移到相關的子類去。
11.5 Push Down Field(字段下移)329
動機:超類中的某個字段只被部分而非全部子類使用
方法:將這個字段移到需要它的那些子類去。
11.6 Extract Subclass(提煉子類)330
動機:類中的某些特性只被部分實例用到。
方法:新建一個子類,將上面所說的那一部分特性移到子類中。
具體情況:
- 并不是出現類型碼就表示需要用到子類,可以在委托和繼承之間做選擇。
- 為子類新建構造函數,
- 子類構造函數與超類構造函數擁有相同的參數列表,并且直接調用超類構造函數
- 如果需要隱藏子類,可使用Replace Constructor with Factory Method(以工廠函數取代構造函數)
- 找出超類調用點
- 如果超類構造函數與子類不同,通過rename method方法可以解決。
- 如果不需要超類實例,可以將超類聲明為抽象類。
- 逐一使用函數下移和字段下移將源類的特性移動到子類。
11.7 Extract Superclass(提煉超類)336
動機:兩個類有相似特性。
方法:為兩個類建立一個超類,將相同特性移至超類。
補充:Extract Class,Extract Subclass,Extract Superclass對比學習。
11.8 Extract Interface(提煉接口)341
動機:多個用戶只使用類接口中的同一子集,或者兩個類的接口有部分相同。
方法:將相同子集提煉到獨立的接口中。
區別:提煉超類是提煉共同代碼,提煉接口時提煉共同接口。
具體動機:如果某個類在不同環境下扮演截然不同的角色,使用接口就是個好主意。接口還能幫助類隱藏一些對外的函數接口。
11.9 Collapse Hierarchy(折疊繼承體系)344
動機:超類和子類之間區別不大。
方法:將它們合為一體。
11.10 Form TemPlate Method(塑造模板函數)344
動機:你有一些子類,其中相應的函數以相同順序執行類似的操作,但各個操作的細節有所不同。
方法:將這些小操作分別放進獨立函數中,并保持它們都有相同的簽名,于是原函數也變得相同了。然后將原函數上移至超類,運用多態來避免重復代碼。這樣的原函數就是Template Method。
原因:雖然使用了繼承,但是函數重復應盡量避免。
11.11 Replace inherited with Delegation(以委托取代繼承)352
動機:某個子類只使用超類接口中一部分,或是根本不需要繼承而來的數據方法:在子類中新建一個字段用以保存超類,調整子類函數,令它委托超類,然后去掉兩者之間的繼承關系。
11.12 Replace Delegation with Inherited(以繼承取代委托)352
動機:在兩個類之間使用委托關系,并經常為整個接口編寫許多極簡單的委托函數,方法:讓委托類繼承受托類。注意:
- 如果并沒有使用受托類的所有函數,那么就不要使用這個方法。因為子類應該總是遵循超類的接口,如果委托過多可以通過Remove Middle Man(160)方法讓客戶端調用受托函數,或者Extract Superclass(336)讓兩個類的接口提煉到超類中;還可以使用Extract Interface(341)方法。
- 如果受托對象被不止一個其他對象共享,而且受托對象是可變的時候,那么這種情況下,不能將委托關系替換為繼承關系,因為這樣就無法共享數據了。數據共享是委托關系的一種重要功能。
posted on 2019-01-16 17:50 zYx.Tom 閱讀(249) 評論(0) 編輯 收藏 所屬分類: 7.學習日志