一、改善代碼的三部曲
《設計模式》-> 《重構》-> 《重構與模式》。也就是設計->重構->重構出新設計。
《設計模式》主要詳細說明20幾種模式,為我們帶來了常見設計問題的經典解決方案,從而改變了整個物件導向開發的面貌。為設計而著。
《重構》改善既有代碼的設計,總結了我們會用到的各種重構手法,為我們帶來了一種改進代碼的高效過程,從而徹底改變了物件導向設計的方式。側重去除壞代碼的味道。
《重構與模式》是設計模式相關的重構。模式不是設計出來的,是重構出來的。好的設計也不是設計出來的,是重構出來的。不要怕改變,只要改變得法,變就不再是災難,而是進步的良機。側重設計模式+重構手段。
在閱讀重構與模式之前,最好熟讀前面兩本:《設計模式》和《重構》。
設計模式代表了傳統的軟體開發思想:好的設計會產生好的軟體,因此在實際開發之前,值得花時間去做一個全面而細緻的設計。重構代表了敏捷式軟體開發 (Agile Software Development)的浪潮:軟體並不是在一開始就可以設計得完美無缺的,因此可以先進行實際開發,然後通過對代碼不斷的進行小幅度的修改來改善其設計。二者從不同角度闡述了設計的重要性。 有些人在編寫任何代碼之前,都要很早地為模式做計劃,而有些人在編寫了大量代碼之後才開始添加模式。
第二種使用模式的方式就是重構,因為是要在不增加系統特性或者不改變其外部行為的情況下改變系統的設計。
有些人在程式中加入模式,只是因為覺得模式能夠使程式更容易修改;更多人這樣做只是為了簡化目前的設計。
如果代碼已經編寫,這兩種情形都是重構,因為前者是通過重構使修改更容易,而後者則是通過重構在修改後進行整理。
雖然模式是在程式中能夠看到的東西,但是模式也是一種程式轉換。
重構是實現設計模式的一種手段,設計模式往往也是重構的目的。
二、重構與模式的緣由
應該通過重構實現模式、趨向模式和去除模式(refactoring to, towards, and away from pattern),而不是在預先設計中使用模式,也不再過早的在代碼中加入模式。這技能避免過度設計,又不至於設計不足。 1.
過度設計:代碼的靈活性和複雜性超出所需。有些開始設計的時候,認為某些地方會頻繁的改動,甚至開始使用了某種設計模式預留擴充,但是後來卻沒怎麼動,也就是導致了廢設計和功能.
2.
設計不足
產生設計不足的原因:
1)程式員沒有時間,沒有抽出時間,或者時間不允許進行重構 2)程式員在何為好的軟體設計方面知識不足
3)程式員被要求在既有系統中快速的添加新功能
4)程式員被迫同時進行太多項目
長期的設計不足,會使軟體開發節奏變成“快,慢,更慢”,可能的後果是:
1.0版本很快就交付了,但是代碼品質很差
2.0版本也交付了,但品質低劣的代碼使我們慢下來
在企圖交付未來版本時,隨著劣質代碼的倍增,開發速度也越來越慢,最後人們對系統、程式員乃至使大家陷入這種境地的整個過程都失去了信心
到了4.0版本時或者之後,我們意識到這樣肯定不行,開始考慮推倒重來 3.
測試驅動開發和持續重構, 測試驅動開發和持續重構提供了一種精益、迭代和訓練有素的編程風格,能夠最大程度的有張有弛,提高生產率。“迅速而又從容不迫” 使用測試驅動開發和持續重構的益處: 1)保持較低的缺陷數量 2)大膽的進行重構 3)得到更加簡單、更加優秀的代碼 4)編程時沒有壓力 模式和重構之間存在著天然聯絡,模式是你想達到的目的地,而重構則是從其他地方抵達這個目的地的條條道路。
4.演化式設計演化式設計即趨向性設計,主要是避免過度設計。通過重構產生設計結構,也就是通過重構實現模式或者重構趨向模式。為設計而設計的思路並不適合大項目,循序漸進從重構到設計模式才是設計模式的王道。
敏捷開發中經常採用的演化式架構設計:
很多程式員可能都遇見過這種事:某塊代碼亟待修改,卻沒有人願意接手。為什麼會這樣?這段代碼正巧是兩個組件間的介面,修改工作太過困難。而在演化式設計中,我們常常會做這種修改。代碼應當是"活的"並且是"可生長"的,決不能無視強烈的變化需求 而保持一成不變。正因為如此,演化式設計可以提高設計品質,進而提高整個系統的品質。
第6章建立
6.1 用Creating Method替換建構函式 當類中有多個建構函式,因此很難決定在開發期間用哪一個時,可以用能夠說明意圖的返回對象執行個體的Creation Method替換建構函式動機: Creation Method——類中的一個靜態或者非靜態負責執行個體化類的新執行個體方法。因Creating Method命名沒有限制,所以可以取最能表達所建立對象的名字。 類中有太多建構函式→提煉類或者提煉子類 或者 用Creation Method替換建構函式來澄清建構函式的意圖優缺點: + 比建構函式能夠更好的表達所建立的執行個體種類 + 避免了建構函式的局限,比如兩個建構函式的參數數目和類型不能相同 + 更容易發現無用的建立代碼 - 建立方式是非標準的,有的用new執行個體化,而有的用Creation Method執行個體化變體: 不需要為每個對象的配置都設立一個Creation Method,非必要情況下可以添加參數來減少Creation Method的數量 當Creation Method過多的分散了類的主要職責是,應該考慮將相關的Creation Method重構為一個Factory
6.2 將建立知識搬移到Factory 當用來執行個體化一個類的資料和代碼在多個類中到處都是時,可以講有關建立的知識搬移到一個Factory中動機: 建立蔓延——將建立的職責放在了不應該承擔對象建立任務的類中,是解決方案蔓延中的一種,一般是之前的設計問題導致。 使用一個Factory類封裝建立邏輯和客戶代碼的執行個體化選項,客戶可以告訴Factory執行個體如何執行個體化一個對象,然後用同一個Factory執行個體在運行時執行執行個體化。 Factory不需要用具體類專門實現,可以使用一個介面定義Factory,然後讓現有的類實現這個介面。 如果Factory中建立邏輯過於複雜,應將其重構為Abstract Factory,客戶代碼可以配置系統使用某個ConcreteFactory(AbstractFactory的一個具體實現)或者預設的ConcreteFactory。 只有確實改進了代碼設計,或者無法直接進行執行個體化時才有足夠的理由進行Factory重構優缺點: + 合并建立邏輯和執行個體化選項 + 將客戶代碼與建立邏輯解耦 - 如果可以直接執行個體化,會使設計複雜化
6.3 用Factory封裝類
當直接執行個體化處在同一包結構中、實現統一介面的多個類。可以把類的建構函式聲明為非公用的,並通過Factory來建立它們的執行個體動機: 可以通過Factory將一組客戶並不需關心的子類屏蔽到包內部。 如果類共用一個通用的公用介面、共用相同的超類、並且處在同一包結構中,該重構可能有用。優缺點: + 通過意圖導向的Creation Method簡化了不同種類執行個體的建立 + 通過隱藏不需要公開的類減少了包的“概念重量” + 協助嚴格執行“面向介面編程,而不是面向實現”這一格言 - 當需要建立新種類的執行個體時,必須更新Creation Method - 當客戶只能獲得Factory的二進位代碼而無法獲得源碼時,對Factory的定製將受到限制
6.4 用Factory Method引入多態建立 當一個層次中的類都相似的實現一個方法,只是對象建立的步驟不同時,可以建立調用Factory Method來處理執行個體化方法的唯一超類版本動機: Factory Method是OOP中最常見的模式,因其提供了多台建立對象的方法 使用Factory Method後的代碼往往比在類中賦值方法來建立自訂對象要簡單 使用Factory Method的主要情況: 當兄弟子類實現了除對象建立步驟外都很相似的方法時 當超類和子類實現了除對象建立步驟外都很相似的方法時優缺點: + 減少因建立自訂對象而產生的重複代碼 + 有效表達了對象建立發生的位置,以及如何重寫對象的建立 + 強制Factory Method使用的類必須實現統一的類型 - 可能會向Factory Method的一些實現者傳遞不必要的參數
6.5 用Builder封裝Composite 當構造Composite是重複的、複雜的且容易出錯的工作時,通過使用Builder處理構造細節來簡化構造過程。動機: 構造Composite是重複的、複雜的、容易出錯的工作,通過使用Builder處理構造細節來簡化構造過程 Builder模式很擅長處理繁重的、複雜的構造步驟。 Builder模式的意圖:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。優缺點: + 簡化了構造Composite的客戶代碼 + 減少了建立Composite的重複和易出錯的本性 + 在客戶代碼和Composite之間實現了松耦合 + 允許對已封裝的Composite或複雜物件建立不同的表示 - 介面可能不會很清楚的表達其意圖
6.6 內聯Singleton 當代碼需要訪問一個對象,但是不需要對象的全域入口時,可以把Singleton的功能搬移到一個儲存並提供對象訪問入口的類中。刪除Singleton。動機: Singleton意圖:確保一個類僅有一個執行個體,並提供一個訪問它的全域訪問點 保持暴露對象和保護對象之間的平衡對維護系統的靈活性是至關重要的 任何全域資料在被證明是無害之前都是有害的 如果遇到本不該實現為Singleton的Singleton,不要猶豫,內聯它!優缺點: + 使對象的協作變得更明顯和明確 + 保護了單一的執行個體,且不需要特殊的代碼 - 當在許多層次間傳遞對象執行個體比較困難的時候,會使設計變得複雜第7章 簡化 我們所編寫的絕大部分代碼都不會從一開始就很簡單。 演算法經常會因為支援多種變化而變得複雜。 控制狀態轉換的轉換的邏輯往往會變得越來越複雜。
7.1 組合方法 當你無法迅速的理解一個方法的邏輯時,把方法的邏輯轉換成幾個同一層面上的、能夠說明意圖的步驟。動機: Composed Method由對其他方法的調用組成,好的Composed Method的代碼都在細節的同一層面上。 Composed Method一般不會引入效能問題優缺點: + 清晰的描述了一個方法所實現的功能以及如何? + 把方法分解成命名良好的、處在細節的同一層面上的行為模組,以此來簡化方法 - 可能會產生過多的小方法 - 可能會使調試變得困難,因為程式的邏輯分散在許多小方法中Composed Method指導原則: Composed Method都很小。一般在5行左右,很少超過10行 重複資料刪除代碼和無作用程式碼。除去明顯的和微妙的代碼重複,除去沒有被使用的代碼,以減少方法的代碼量 表達意圖。清楚的命名程式中的變數、方法和參數,使它們明確表達意圖。 簡化。轉碼,使它儘可能簡單。 使用細節的統一層面。當把一個方法分解成一組行為時,要保證這些行為在細節的相似層面上。
7.2 用Strategy替換條件邏輯 當方法中條件邏輯控制著應該執行計算的哪個變體時,為每個變體建立一個Strategy並使方法把計算委託到Strategy執行個體。 動機: ——為演算法的各個變體產生一系列的類,並用Strategy的一個執行個體裝配主類,主類在運行時委託到該Strategy執行個體 複雜的條件邏輯是最常導致複雜度上升的地點之一優缺點: + 通過減少或去除條件邏輯使演算法變得清晰易懂 + 通過把演算法的變體搬移到類層次中簡化了類 + 允許在運行時用一種演算法替換另一種演算法 - 當應用基於繼承的解決方案或“
簡化條件運算式”中的重構更簡單時,會增加設計的複雜度 - 增加了演算法如何擷取或接收上下文類資料的複雜度
7.3 將裝飾功能搬移到Decorator 當代碼向類和核心職責提供裝飾功能時,可以考慮將裝飾代碼搬移到Decorator 無論多麼喜歡一個模式,不要在不必要的時候使用它優缺點: + 把裝飾功能從類中移除,從而簡化類 + 有效把類的核心職責和裝飾功能區分開來 + 可以去除幾個相關類中重複的裝飾邏輯 - 改變了被裝飾對象的類型 - 會使代碼變得更難理解和調試 - 當Decorator組合產生負面影響的時候,會增加設計的複雜度
7.4 用State替換狀態改變條件陳述式 當控制一個對象狀態轉換的條件運算式過於複雜時,可以考慮用處理特殊狀態轉換的State類替換條件陳述式優缺點: + 減少或去除狀態改變條件邏輯 + 簡化了複雜的狀態改變邏輯 + 提供了觀察狀態改變邏輯的很好的鳥瞰圖 - 當狀態轉換邏輯已經易於理解的時候,會增加設計的複雜度
7.5 用Composite替換隱含樹 當用原生標記法隱含的形成了樹結構時,可以考慮用Composite替換這個原生標記法 優缺點: + 封裝重複的指令,如格式化、添加或刪除結點 + 提供了處理相似邏輯增長的一般性方法 + 簡化了客戶代碼的構造職責 - 當構造隱式樹更簡單的時候,會增加設計的複雜度
7.6 用Command替換條件發送器 當條件邏輯用來調度請求和執行操作時,為每個動作建立一個Command。把這些Command儲存在一個集合中,並用擷取及執行Command的代碼替換條件邏輯。 為每個動作建立一個Command,把這些Command儲存在一個集合中,並用擷取及執行Command的代碼替換條件邏輯優缺點: + 提供了用統一方法執行不同行為的簡單機制 + 允許在運行時改變所處理的請求,以及如何處理請求 + 僅僅需要很少的代碼實現 - 當條件發送器已經足夠的時候,會增加設計的複雜度
第8章 泛化 泛化是把特殊代碼轉換成通用目的代碼的過程。泛化代碼的產生往往的重構的結果。
8.1 形成Template Method 當子類中的兩個方法以相同的順序執行相似的步驟,但是步驟並不完全相同。通過把這些步驟提取成具有相同簽名的方法來泛化這兩個方法,然後上移這些泛化方法,形成Template Method。 優缺點: + 通過把不變行為搬移到超類,去除子類中的重複代碼 + 簡化並有效表達了一個通用演算法的步驟 + 允許子類很容易的定製一個演算法 - 當為了產生演算法,子類必須實現很多方法的時候,會增加設計的複雜度
8.2 提取Composite 當一個類階層中的多個子類實現了同一個Composite時,可以提取一個實現該Composite的超類 優缺點: + 去除重複的類儲存邏輯和類處理邏輯 + 能夠有效表達類處理邏輯的可繼承性
8.3 用Composite替換一/多之分 當類使用不同的代碼處理單一對象與多個對象時,用Composite能夠產生既可以處理單一對象又可以處理多個對象的代碼 優缺點: + 去除與處理一個或多個對象相關聯的重複代碼 + 提供處理一個或多個對象的統一方法 + 支援處理多個對象的更豐富的方法 - 可能會在Composite的構造過程中要求型別安全的運行時檢查
8.4 用Observer替換硬式編碼通知 當子類通過寫入程式碼來通知另一個類的執行個體時可以去除這些子類,並使其超類能夠通知一個或多個實現了Observer介面的類 優缺點: + 使主題及其觀察者訪問鬆散耦合 + 支援一個或多個觀察者 - 當硬式編碼通知已經足夠的時候,會增加設計的複雜度 - 當出現串聯通知的時候,會增加代碼的複雜度 - 當觀察者沒有從它們的主題中被刪除的時候,可能會造成資源泄漏
8.5 通過Adapter統一介面 當客戶代碼與兩個類互動,其中的一個類具有首選介面,可以用一個Adapter統一介面 動機: 當下麵條件都為真時,重構Adapter就是有用的: 兩個類所做的事情相同或相似,但是具有不同的介面 如果類共用同一個介面,客戶代碼會更簡單、更直接、更緊湊 無法輕易改變其中一個類的介面,因為它是第三方庫中的一部分,或者它是一個已經被其他客戶代碼廣泛使用的架構的一部分,或者無法獲得源碼優缺點: + 使客戶代碼可以通過相同的介面與不同的類互動,從而去除或減少重複代碼 + 使客戶代碼可以通過公用的介面與多個對象互動,從而簡化了客戶代碼 + 統一了客戶代碼與不同類的互動方式 - 當類的介面可以改變的時候,會增加設計的複雜度
8.6 提取Adapter 當一個類適配了多個版本的組件、類庫、API或其他實體時,可以為組件、類庫、API或其他實體的每個版本提取一個Adapter Adapter用來適配對象,Facade用來適配整個系統,Facade通常用來與遺留系統進行互動優缺點: + 隔離了不同版本的組件、類庫或API之間的不同之處 + 使類只負責適配代碼的一個版本 + 避免頻繁的修改代碼 - 如果某個重要行為在Adapter中停用話,那麼客戶代碼將無法執行這一重要行為
8.7 用Interpreter替換隱式語言 當類中的許多方法組合成了一種隱式語言的元素,可以為隱式語言的元素定義類,這樣就可以通過類執行個體組合,形成易於理解的運算式 優缺點: + 比隱式語言更好的支援語言元素的組合 + 不需要解析新的代碼來支援語言元素的新組合 + 允許行為的運行時配置 - 會產生定義語言和修改客戶代碼的開銷 - 如果語言很複雜,則需要很多的編程工作 - 如果語言本身就很簡單,則會增加設計的複雜度第9章 保護
9.1 用類替換類型代碼 欄位的類型無法保護它免受不正確的複製和非法的等同性比較,可以把欄位的型別宣告為類,從而限制複製和等同性比較 優缺點: + 更好的避免非法賦值和比較 - 比使用不安全類型要求更多的代碼
9.2 用Singleton限制執行個體化 代碼建立了一個對象的多個執行個體,並導致記憶體使用量過多和系統效能下降時,可以用Singleton替換多個執行個體 不要做不成熟的代碼最佳化,經過不成熟最佳化的代碼比未最佳化的代碼更難於重構。在代碼最佳化之前,你會發現更多可以改進的地方優缺點: + 改進效能 - 在任何地方都可以很容易的訪問。在很多情況下,這可能是設計的缺點 - 當對象含有不能共用的狀態時,本重構無效
9.3 引入Null Object 當代碼中到處都是處理null欄位或變數的重複邏輯時,將null邏輯替換為一個Null Object,一個提供正確null行為的對象 優缺點: + 不需要重複的null邏輯就可以避免null錯誤 + 通過最小化null測試簡化了代碼 - 當系統不太需要null測試的時候,會增加設計的複雜度 - 如果程式員不知道Null Object的存在,就會產生多餘的null測試 - 使維護變得複雜,擁有超類的Null Object必須重寫所有新繼承到的公用方法
第10章 聚集操作
10.1 將聚集操作搬移到Collecting Parameter 有一個很大的方法將資訊聚集到一個局部變數中時,可以把結果聚集到一個Collecting Parameter中,並將它傳入被提煉出的方法中 優缺點: + 協助我們把很大的方法轉換成更小的,更簡單的多個方法 - 使結果代碼運行得更快
10.2 將聚集操作搬移到Visitor 有一個方法從不同的類中
聚集資訊,可以把聚集工作搬移到一個能夠訪問每個類以便採集資訊的Visitor中。 優缺點: + 調節多個演算法,使其適用於不同的對象結構 + 訪問相同或不同繼承結構中的類 + 調用不同類上的類型特定方法,無需類型轉換 - 當可以使用通用介面把互不相同的類變成相似類的時候,會增加代碼的複雜度 - 新的可訪問類需要新的接收方法,每個Visitor中需要新的存取方法 - 可能會破壞訪問類的封裝性
第11章 使用重構
11.1 鏈建構函式 有很多包含重複代碼的建構函式時,可以把建構函式連結起來,從而獲得最少的代碼重複。
11.2 統一介面 當需要一個與其子類具有相同介面的超類或介面時,可以找到所有子類含有而超類沒有的公用方法,把這些方法複製到超類中,並修改每個方法,使其執行空行為。
11.3 提取參數 當一個方法或建構函式將一個欄位賦值為一個局部執行個體化的值時,可以把賦值聲明的右側提取到一個參數中,並通過客戶代碼提供的參數對欄位進行賦值。