重构-改善既有代码的设计:对象之间移动特性的八种方法(五)
? ? ?在面向對象編程過程中,明確該對象的職責。類應該是:做自己該做的事,應盡該盡的義務,
1.Move Method 移動函數
類的行為做到單一職責?不要越俎代庖:?你的程序中,有個函數與其所駐類之外的另一個類進行更多的交流:調用后者,或被后者調用。 在該函數最常用引用的類中建立一個有著類似行為的新函數。將舊函數編程一個單純的委托函數,或是將舊函數完全移除。
? ? ? “搬移函數”是重構理論的支柱。如果一個類有太多行為,或如果一個類與另一個類有太多合作而形成高度耦合,就需要搬移函數。通過這種手段,可以使系統中的類更簡單,這些類最終也將更干凈利落的實現系統交付的工作。
? ? ? ? 瀏覽類的所有函數,從中找出這樣的函數:使用另一個對象的次數比使用自己所駐對象的次數還多。一旦移動了一些字段,就該做這樣的檢查。一旦發現有可能搬移的函數,就觀察調用它的那一端、它調用的那一端,已經繼承體系中它的任何一個重定義函數。然后,會根據“這個函數與哪個對象的交流比較多”,決定其移動路徑。
? ? ? ? ?這往往不是容易做出的決定。如果不能肯定是否應該移動一個函數,就繼續觀察其他函數。移動其他函數往往會讓這項決定變得容易一些。有時候,即使你移動了其他函數,還是很難對眼下這個函數做出決定。其實這也沒什么大不了的。如果真的很難做出決定,那么也許“移動這個函數與否”并不是那么重要。所以,就憑本能去做,反正以后總是可以修改的。
2.Move Field 搬移字段
類的屬性就應去改去的地方:你的程序中,某個字段被其所駐類之外的另一個類更多的用到。在目標類建立一個新字段,修改源字段的所有用戶,令它們改用新字段
? ? ? ? 在類之間移動狀態和行為,是重構過程中必不可少的措施。隨著系統發展,你會發現自己需要新的類,并需要將現有的工作責任拖到新的類中。在這個星期看似合理而正確的設計決策,到了下個星期可能不再正確。這沒問題,如果你從來沒遇到這種情況,那才有問題。
???????如果發現對于一個字段,在其所駐類之外的另一個類中有更多函數使用了它,就考慮搬移這個字段。上述所謂“使用”可能是通過設值/取值函數間接進行的。也可能移動該字段的用戶(某個函數),這取決于是否需要保持接口不受變化。如果這些函數看上去很適合待在原地,就選擇搬移字段。
???????使用Extract Class (提煉類)時,也可能需要搬移字段。此時可以先搬移字段,然后搬移函數。
3.Extract Class提煉類
?一個類應該是一個清楚地抽象,處理一些明確的責任,把大類分解成小類:某個類做了應該由兩個類做的事。建立一個新類,將相關的字段和函數從舊類搬移到新類。
? ? ? ? 一個類應該是一個清楚地抽象,處理一些明確的責任。但是在實際工作中,類會不斷成長擴展。你會在這兒加入一些功能,在哪加入一些數據。給某個類添加一項新責任時,你會覺得不值得為這項責任分離出一個單獨的類。于是,隨著責任不斷增加,這個類會變得過分復雜。
很快,你的類就會變成一團亂麻。
? ? ? ? 這樣的類往往含有大量函數和數據。這樣的類往往太大而不易理解。此時你需要考慮哪些部分可以分離出去,并將它們分離到一個單獨的類中。如果某些數據和某些函數總是一起出現,某些數據經常同時變化甚至彼此依賴,這就表示你應該將它們分離出去。一個有用的測試就是問自己,如果搬移了某些字段和函數,會發生什么事?其他字段和函數是否因此變得無意義。另一個往往在開發后期出現的信號時類的子類化方式。如果你發現子類化只影響類的部分特性,或如果你發現某些特性需要以一種方式來子類化,某些特性則需要以另一種方式子類化,這就意味著你需要分解原來的類。
4.?Inline Class 將類內聯化
小類直接移到大類:某個類沒有做太多事情。將這個類的所有特性搬移到另一個類中,然后移除原類。
? ? ? ?Inline Class (將類內聯化)正好于Extract Class (提煉類)相反。如果一個類不再承擔足夠責任、不再有單獨存在的理由(這通常是因為此前的重構動作移走了這個類的責任),就挑選這個“萎縮類”的最頻繁的用戶(也是個類),以Inline Class (將類內聯化)手法將“萎縮類”塞進另一個類中。
5.?Hide Delegate 隱藏委托關系
客戶通過一個委托類在調用另一個對象。在服務類上建立客戶所需的所有函數,用以隱藏委托關系。
? ? ? ?封裝”即使不是對象的關鍵特征,也是關鍵特征之一?!胺庋b”意味每個對象都有應該盡可能少了解系統的其他部分。如此一來,一旦發生變化,需要了解這一變化的對象就會比較少,這會使變化較容易進行。
???????如果某個客戶先通過服務對象的字段得到另一個對象,然后調用后者的函數,那么客戶就必須知曉這一層委托關系。萬一委托關系發生變化,客戶也得相應變化。你可以在服務對象上放置一個簡單的委托函數,將委托關系隱藏起來,從而去除這種依賴。這么一來,即便將來發生委托關系上的變化,變化也將被限制在服務對象中,不會波及客戶。
???????對于某些或全部客戶,你可能會發現,有必要先使用Extract Class (提煉類)。一旦你對所有客戶都隱藏了委托關系,就不再需要在服務對象的接口中公開被委托對象。
6.Remove middle Man 移除中間人
某個類做了過多的簡單委托動作。 讓客戶直接調用受托類。
? ? ? ? 在Hide Delegate (隱藏委托關系)的“動機”中,談到了“封裝委托對象”的好處。但是這層封裝也是要付出代價的,它的代價是:每當客戶要使用受托類的新特性時,你就必須在服務端添加一個簡單委托函數。隨著委托類的特性(功能)越來越多,這一過程讓你痛苦不已。服務類完全變成了“中間人”,此時你就應該讓客戶直接調用受托類。
?????很難說什么程度的隱藏才是合適的。還好,有了Hide Delegate (隱藏委托關系)和Remove Middle Man (移除中間人),你大可不必操心這個問題。因為你可以在系統運行過程中不斷進行調整。隨著系統的變化,“合適的隱藏程度”這個尺度也相應改變。6個月前恰如其分的封裝,現今可能就顯得笨拙。重構的意義在于:你永遠不必說對不起—只要把出問題的地方修補好就行了。
7.?Introduce Foreign Method 引入外加函數
你需要為提供服務的類增加一個函數,但你無法修改這個類。在客戶類中建立一個函數,并以第一參數形式傳入一個服務類實例。
? ? ? 這種事情發生了太多次了,你正在使用一個類,它真的很好,為你提供了需要的所有服務。而后,你又需要一項新服務,這個類卻無法供應。于是你開始咒罵“為什么不能做這件事?”如果可以修改源碼,你便可以自行添加一個新函數;如果不能,你就得在客戶端編碼,補足你要的那個函數。
???????如果客戶類只使用這項功能一次,那么額外編碼工作沒什么大不了,甚至可能根本不需要原本提供服務的那個類。然而,如果你需要多次使用這個函數,就得不斷重復這些代碼。重復代碼是軟件萬惡之源。這些重復代碼應該被抽出來放進一個函數中。進行本項重構時,如果你以外加函數實現一項功能,那就是一個明確信號:這個函數原本應該在提供服務的類中實現。
???????如果你發現自己為一個服務類建立了大量外加函數,或者發現有許多類需要同樣的外加函數,就不應該再使用本項重構,而應該使用 Introduce Local Extension (引入本地擴展)。
???????但是不要忘記:外加函數終歸是權宜之計。如果有可能,你仍然應該將這些函數搬移到它們的理想家園。如果由于代碼所有權的原因使你無法做這樣的搬移,就把外加函數交給服務類的提供者,請他幫你在服務類中實現這個函數。
8.Introduce Local Extension 引入本地擴展
你需要為服務類提供一些額外函數,但你無法修改這個類。建立一個新類,使它包含這些額外函數。讓這個擴展品成為源類的子類或包裝類。? ? ? 類的作者無法預知未來,他們常常沒能為你預先準備一些有用的函數。如果你可能修改源碼,最后的辦法就是直接加入自己需要的函數。但你經常無法修改源碼。如果只需要一兩個函數,你可以使用 Introduce Foreign Method (引入外加函數)。但如果你需要的額外函數超過2個,外加函數就很難控制它們了。所以你需要將這些函數組織在一起,放到一個恰當的地方去。要達到這個目的,2種標準對象技術—子類化(subclassing)和包裝(wrapping)是顯而易見的辦法。這種情況下,把子類化和包裝類統稱為本地擴展。
? ? ? ??所謂本地擴展是一個獨立的類,但也是被擴展類的字類型:它提供源類的一切特性,同時額外添加新特性。在任何使用源類的地方,你都可以使用本地擴展取而代之。
? ? ??使用本地擴展使你得以堅持:函數和數據應該被統一封裝“的原則。如果你一直把本該放在擴展類中的代碼零散的放置于其他類中,最終只會讓其他這些類變得過分復雜,并使得其他函數難以被復用。
? ? ? ?在子類和包裝類之間做選擇時,首選子類。因為這樣的工作量比較少。制作子類的最大障礙在于,它必須在對象創建期實施。如果可以接管對象創建過程,那當然沒問題;但如果你想在對象創建之后再使用本地擴展,就有問題了。此時,子類化方案還必須產生一個子類對象,這種情況下,如果有其他對象引用了舊對象,我們就同時有2個對象保存了原數據。如果原數據是不可修改的,那也沒問題??梢苑判倪M行復制;但如果原數據允許修改,問題就來了,因為一個修改動作無法同時改變2份副本。這時候就必須改用包裝類。使用包裝類時,對本地擴展的修改會波及原對象,反之亦然。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的重构-改善既有代码的设计:对象之间移动特性的八种方法(五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重构-改善既有代码的设计:简化函数调用
- 下一篇: 重构-改善既有代码的设计:重新组织函数的