一、改善代碼的三部曲
《設計模式》-> 《重構》-> 《重構與模式》。也就是設計->重構->重構出新設計。
《設計模式》主要詳細說明20幾種模式,為我們帶來了常見設計問題的經典解決方案,從而改變了整個面向對象開發的面貌。為設計而著。
《重構》改善既有代碼的設計,總結了我們會用到的各種重構手法,為我們帶來了一種改進代碼的高效過程,從而徹底改變了面向對象設計的方式。側重去除壞代碼的味道。
《重構與模式》是設計模式相關的重構。模式不是設計出來的,是重構出來的。好的設計也不是設計出來的,是重構出來的。不要怕改變,只要改變得法,變就不再是災難,而是進步的良機。側重設計模式+重構手段。
在閱讀重構與模式之前,最好熟讀前面兩本:《設計模式》和《重構》。
設計模式代表了傳統的軟件開發思想:好的設計會產生好的軟件,因此在實際開發之前,值得花時間去做一個全面而細致的設計。重構代表了敏捷軟件開發的浪潮:軟件并不是在一開始就可以設計得完美無缺的,因此可以先進行實際開發,然后通過對代碼不斷的進行小幅度的修改來改善其設計。二者從不同角度闡述了設計的重要性。
有些人在編寫任何代碼之前,都要很早地為模式做計劃,而有些人在編寫了大量代碼之后才開始添加模式。
第二種使用模式的方式就是重構,因為是要在不增加系統特性或者不改變其外部行為的情況下改變系統的設計。
有些人在程序中加入模式,只是因為覺得模式能夠使程序更容易修改;更多人這樣做只是為了簡化目前的設計。
如果代碼已經編寫,這兩種情形都是重構,因為前者是通過重構使修改更容易,而后者則是通過重構在修改后進行整理。
雖然模式是在程序中能夠看到的東西,但是模式也是一種程序轉換。
重構是實現設計模式的一種手段,設計模式往往也是重構的目的。
二、重構與模式的緣由
應該通過重構實現模式、趨向模式和去除模式(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 提取參數
當一個方法或構造函數將一個字段賦值為一個局部實例化的值時,可以把賦值聲明的右側提取到一個參數中,并通過客戶代碼提供的參數對字段進行賦值。
轉自:http://blog.csdn.net/hguisu/article/details/7658644
原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2806