### Move Method(搬移函數)
(譯注:本節大量保留class,method,source,target等字眼)
你的程序中,有個函數與其所駐class之外的另一個class進行更多交流:調用后者,或被后者調用。
在該函數最常引用(指涉)的class中建立一個有著類似行為的新函數。將舊函數變成一個單純的委托函數(delegating method),或是將舊函數完全移除。

**動機(Motivation)**
「函數搬移」是重構理論的支柱。如果一個class有太多行為,或如果一個class與另一個class有太多合作而形成高度耦合(highly coupled),我就會搬移函數。通過這種手段,我可以使系統中的classes更簡單,這些classes最終也將更干凈利落地實現系統交付的任務。
常常我會瀏覽class的所有函數,從中尋找這樣的函數:使用另一個對象的次數比使用自己所駐對象的次數還多。一旦我移動了一些值域,就該做這樣的檢查。一旦發現「有可能被我搬移」的函數,我就會觀察調用它的那一端、它調用的那一端,以及繼承體系中它的任何一個重定義函數。然后,我會根據「這個函數與哪個對象的交流比較多」,決定其移動路徑。
這往往不是一個容易做出的決定。如果不能肯定是否應該移動一個函數,我就會繼續觀察其他函數。移動其他函數往往會讓這項決定變得容易一些。有時候,即使你移動了其他函數,還是很難對眼下這個函數做出決定。其實這也沒什么大不了的。 如果真的很難做出決定,那么或許「移動這個函數與否」并不那么重要。所以,我會憑本能去做,反正以后總是可以修改的。
**作法(Mechanics)**
- 檢查source class定義之source method所使用的一切特性(features),考慮它們是否也該被搬移。(譯注:此處所謂特性泛指class定義的所有東西,包括值域和函數。)
- 如果某個特性只被你打算搬移的那個函數用到,你應該將它一并搬移。如果另有其他函數使用了這個特性,你可以考慮將使用該特性的所有函數全都一并搬移。有時候搬移一組函數比逐一搬移簡單些。
- 檢查source class的subclass和superclass,看看是否有該函數的其他聲明。
- 如果出現其他聲明,你或許無法進行搬移,除非target class也同樣表現出多態性(polylmorphism〕。
- 在target class中聲明這個函數。
- 你可以為此函數選擇一個新名稱——對target class更有意義的名稱。
- 將source method的代碼拷貝到target method中。調整后者,使其能在新家中正常運行。
- 如果target method使用了source特性,你得決定如何從target method引用source object。如果target class中沒有相應的引用機制,就把source object reference當作參數,傳給新建立的target class。
- 如果source method包含異常處理式(exception handler),你得判斷邏輯上應該由哪個來處理這一異常。如果應該由source class負責,就把異常處理式留在原地。
- 編譯target class。
- 決定如何從source正確引用target object。
- 可能會有一個現成的值域或函數幫助你取得target class。如果沒有,就看能否輕松建立一個這樣的函數。如果還是不行,你得在source class中新建一個新值域來保存target object。這可能是一個永久性修改,但你也可以讓它保持暫時的地位,因為后繼的其他重構項目可能會把這個新建值域去掉。
- 修改source method,使之成為一個delegating method(純委托函數〕。
- 編譯,測試。
- 決定「刪除source method」或將它當作一個delegating method保留下來。
- 如果你經常要在source object中引用target method,那么將source method作為delegating method保留下來會比較簡單。
- 如果你想移除source method,請將source class中對source method的所有引用動作,替換為「對target method的引用動作」。
- 編譯,測試。
**范例(Examples)**
我用一個表示「帳戶」的account class來說明這項重構:
~~~
class Account...
double overdraftCharge() { //譯注:透支金計費,它和其他class的關系似乎比較密切。
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7) result += (_daysOverdrawn - 7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += overdraftCharge();
return result;
}
private AccountType _type;
private int _daysOverdrawn;
~~~
假設有數種新帳戶,每一種都有自己的「透支金計費規則」。所以我希望將overdraftCharge()搬移到AccountType class去。
第一步要做的是:觀察被overdraftCharge()使用的每一特性(features),考慮是否值得將它們與overdraftCharge()—起移動。此例之中我需要讓daysOverdrawn值域留在Account class,因為其值會隨不同種類的帳戶而變化。然后,我將overdraftCharge()函數碼拷貝到AccountType中,并做相應調整。
~~~
class AccountType...
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
return result;
}
else return daysOverdrawn * 1.75;
}
~~~
在這個例子中,「調整」的意思是:(1)對于「使用AccountType特性」的語句,去掉_type;(2)想辦法得到依舊需要的Account class特性。當我需要使用source class特性,我有四種選擇:(1)將這個特性也移到target class;(2)建立或使用一個從target class到source的引用〔指涉)關系;(3)將source object當作參數傳給target class;(4)如果所需特性是個變量,將它當作參數傳給target method。
本例中我將_daysOverdrawn變量作為參數傳給target method(上述(4))。
調整target method使之通過編譯,而后我就可以將source method的函數本體替換為一個簡單的委托動作(delegation),然后編譯并測試:
~~~
class Account...
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
~~~
我可以保留代碼如今的樣子,也可以刪除source method。如果決定刪除,就得找出source method的所有調用者,并將這些調用重新定向,改調用Account的bankCharge():
~~~
class Account...
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
~~~
所有調用點都修改完畢后,我就可以刪除source method在Account中的聲明了。我可以在每次刪除之后編譯并測試,也可以一次性批量完成。如果被搬移的函數不是private,我還需要檢查其他classes是否使用了這個函數。在強型(strongly typed) 語言中,刪除source method聲明式后,編譯器會幫我發現任何遺漏。
此例之中被移函數只取用(指涉〕一個值域,所以我只需將這個值域作為參數傳給target method就行了。如果被移函數調用了Account中的另一個函數,我就不能這么簡單地處理。這種情況下我必須將source object傳遞給target method:
~~~
class AccountType...
double overdraftCharge(Account account) {
if (isPremium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7)
result += (account.getDaysOverdrawn() - 7) * 0.85;
return result;
}
else return account.getDaysOverdrawn() * 1.75;
}
~~~
如果我需要source class的多個特性,那么我也會將source object傳遞給target method。不過如果target method需要太多source class特性,就得進一步重構。通常這種情況下我會分解target method,并將其中一部分移回source class。
- 譯序 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 )
- 小結
- 章節十五 集成
- 參考書目