重構改善既有代碼的設計

重構改善既有代碼的設計

第二章 重構原則

重構
  • 重構[名詞]:對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。
  • 重構[動詞]:使用一系列重構手法,在不改變軟件可古納差行爲的前提下,調整其結構。
重構的目的
  • 使軟件更容易被理解和修改。你能夠在軟件內部作不少修改,但必須對軟件可觀察的外部行只形成很小變化,或甚至不形成變化。與之造成對比的事性能優化。和重構同樣,性能優化一般不會改變組件的行爲(除了執行速度),只會改變其內部結構。但二者出發點不一樣:性能優化每每使代碼較難理解,但爲了獲得所須要的性能你不得不那麼作。
爲何要重構
  • 重構改進軟件設計
    • 爛代碼、重複性、程序結構複雜紊亂、維護困難
  • 重構使軟件更容易理解
    • 第二個程序員接管、修改大於理解、從新查閱代碼
  • 重構幫助找到bug
    • 調試、強健的代碼
  • 重構提升編程速度
    • 快速編程
什麼時候重構
  • 三次法則[事不過三,三則重構]程序員

    • 第一次作某件事時只管去作
    • 第二次作相似的事會產生反感
    • 第三次再作相似的事,你就應該重構
  • 添加新功能時重構算法

    • 整理代碼結構,便於理解、原有代碼設計沒法幫助我輕鬆添加所需的特性
  • 修補錯誤時重構數據庫

    • 代碼可讀性
  • 複審代碼時重構express

    • 讓別人更好的理解本身的代碼、結對編程
爲何程序會難以理解
  • 難以閱讀的程序,難以修改
  • 邏輯重複的程序,難以修改
  • 添加新行爲時須要修改已有代碼的程序,難以修改
  • 帶複雜條件邏輯的程序,難以修改
所以咱們但願的程序是
  • 容易閱讀
  • 全部邏輯都只在惟一地點指定
  • 新的改動不會危及現有行爲
  • 儘量簡單表達條件邏輯
修改接口
  • 關於對象,另外一件重要的事是:他們容許你分開修改軟件模塊餓的實現和接口。你能夠安全地修改對象內部實現而不影響他人,但對於接口要特別謹慎--若是接口被修改了,任何事均可能發生。
如何修改已發佈接口
  • 讓就接口調用新接口。當你要修改某個函數名稱時,請留下舊函數,讓它調用新函數。千萬不要複製函數的實現,那會讓你陷入重複代碼的泥沼中難以自拔。你還應該使用Java提供的deprecation(不建議使用)設施,將舊接口標記爲deprecated。這麼一來你的調用者舊會注意到它了。
  • 不要過早發佈接口。請修改你的代碼全部權政策,使重構更順暢。
難以重構的思路
  • 先想象重構的狀況。考慮候選設計方案時,我會問本身:將某個設計重構爲另外一個設計的難度有多大?若是看上去很簡單,我就沒必要太擔憂選擇是否得當,因而我就會選擇最簡單的設計,哪怕它不能覆蓋全部潛在需求也不要緊。但若是預先看不到簡單的重構方法,我就會設計上投入更多力氣。
什麼時候不應重構
  • 現有代碼根本不能正常運行。
  • 重構的代碼太混亂,還不如重寫一個來的簡單。
  • 一個折中的辦法就是:將「大塊頭軟件」重構爲封裝良好的小型組件。而後你就能夠逐一對組件作出「重構或重建」的決定。
  • 項目已近最後限期,避免重構。

重構與設計

  • 有一種觀點認爲:重構能夠取代預先設計。這意思是你根本沒必要作任何涉及,只管按照最初想法開始編碼,讓代碼有效運做,而後再將它重構成型。事實上這種辦法真的可行。的確有人這麼作,最後得到設計良好的軟件。
  • 有些編程是使用CRC卡或相似的東西來檢驗各類不一樣想法,而後才獲得第一個可被接受的解決方案,而後才能開始編碼,而後才能重構。關鍵在於:重構改變了預先設計的角色。

CRC卡編程

第三章 代碼的壞味道

重讀代碼[Duplicated Code]
  • 重複的函數
    • 解決:抽離成一個函數
  • 兄弟類中出現相同表達式
    • 解決:把相同的函數推向父類
  • 絕不相關的兩個類
    • 解決:使用抽象類,兩個類同繼承抽象類
過長函數[Long Method]
分解函數的原則
  • 每當感受須要以註釋說明點什麼的時候,咱們就把須要說明的東西寫進一個獨立函數中,並以其用途(而非實現手段)命名。咱們能夠對一組甚至短短一行代碼作這件事。哪怕替換後的函數調用動做比函數自身還長,只要函數名稱可以解釋其用途,咱們也該絕不猶豫地name作。關鍵不在於函數的長度,而在於函數「作了什麼」和「如何作」之間的語義距離。
  • 百分之九十九的場合裏,要把函數變小,只要使用Extract Method。找到函數中適合集中在一塊兒的部分,將它們提煉出來造成一個函數。
如何肯定該提煉哪一段
  • 尋找註釋。它們一般能指出代碼用途和實現手法之間的語義距離。若是代碼前方有註釋,就是在提醒你能夠將這段代碼替換成一個函數,並且能夠在註釋的基礎上給這個函數命名。就算只有一行代碼,若是它須要以註釋來講明,那也值得被提煉出來。
  • 條件表達式和循環經常也是提煉的信號。你應將循環和其內的代碼提煉到一個獨立函數中。
過大的類
  • 單個類作太多的事情,其內每每就會出現太多實例變量。一旦如此,重複的代碼也就會接踵而至。
  • 可使用抽象類將幾個變量一塊兒提煉到這個新類中。一般若是類內的數個變量有相同的前綴或字尾,這就意味有機會把它們提煉到某個組件內。若是這個組件適合做爲一個子類,你會發現抽象子類每每比較簡單。
「太多實例變量」
  • 解決:最簡單的是把多餘的東西分解與內部類。若是有5個「百行函數」,它們之中不少代碼都相同,name或許你能夠把它們編程5個「十行函數」和提煉出來的「雙行函數」。
「擁有太多實例變量」
  • 先肯定客戶端如何使用它們,而後運用抽象接口爲每一種使用方式提煉出一個接口。
過長參數列[Long Parameter List]
發散式變化[Divergent Change]
  • 若是當你看到一個類,「若是新加入一個數據庫,我必須修改這三個函數;若是新出現一種金融工具,我必須修改4個函數」。那麼此時也許將這個對象分紅兩個會更好,這麼一來每一個對象就能夠只因一種變化而須要修改。
  • 運用抽象類將它們提煉到了另外一個類中。
霰彈式修改
  • 每遇到某種變化,你都必須在許多不一樣的類內作出許多小修改。
  • 解決:把全部須要修改的代碼放到同一個類。若是眼下沒有合適的類能夠安置這些代碼,就創造一個。一般能夠運用Inline Class把一系列相關行爲放到同一個類。這可能形成少許的重複變動動做,也能夠輕易處理它。
依戀情節[Feature Envy]
一個函數每每會用到幾個類的功能,那麼它究竟應該被置於何處呢
  • 判斷哪一個類擁有最多被此函數使用的數據,而後把這個函數和那些數據擺一塊兒。
  • 若是先以Extract Method將這個函數分解成數個小函數並分別置放雨不一樣地點,上述步驟也就比較容易完成了。
  • 最根本的原則是:將老是一塊兒變化的東西放在一起。數據和引用這些數據的行爲老是一塊兒變化的,但也有例外。若是例外出現,咱們就搬移那些行爲,保持變化只在一地發生。
數據泥團[Data Clumps]
經常看到相同的三四項數據:兩個類中相同的字段、許多函數簽名中相同的參數。這些老是綁在一塊兒出現的數據真應該擁有屬於它們本身的對象
  • 首先找出這些數據以字段形式出現的地方,運用Extra Class將它們提煉到一個獨立對象中。
  • 而後將注意力轉移到函數簽名上,運用Introduce Parameter Object或Preserve Whole Object爲它減肥。這樣作的直接好處是能夠將不少參數列縮短,簡化函數調用。
  • 一個好的評判方法是:刪除衆多數據中的一項。這麼作,其餘數據有沒有於是失去意義?若是它們再也不有意義,這就是個明確暗號:你應該爲它們產生一個新對象。
基本類型偏執[Primitive Obsession]
大多數編程環境都有兩種數據
  • 結構類型容許你將數據組織成有意義的形式。數組

  • 基本類型則是構成結構類型的積木塊。安全

  • 若是你有一組應該老是被放在一塊兒的字段,可運用Extract Class。性能優化

  • 若是你在參數列中看到基本型數據,不妨試試Introduce Parameter Object。session

  • 若是你發現本身正從數組中挑選數據,可運用Replace Array with Object。app

switch驚悚現身[Switch Statements]
面向對象程序的一個最明顯的特徵就是:少用switch(或case)語句。從本質上說,switch語句的問題在於重複。你常會發現一樣的switch語句散步於不一樣點。若是要爲它添加一個新的case子句,就必須找到全部switch語句並修改它們,面向對象中的多態概念能夠爲此帶來優雅的解決方法。
  • 一旦看到switch就應該考慮以多態來替換它。
平行繼承體系[Parallel Inheritance Hierarchies]
每當你爲這個類增長一個子類,必須爲另外一個類增長一個子類。若是你發現某個繼承體系的類名稱前綴和另外一個繼承體系的類名稱前綴徹底相同,即是聞到了壞的味道。
  • 讓一個繼承體系的實例引用到另外一個繼承體系的使用。
冗贅類[Lazy Class]
若是一個雷的所得不值其身價,它就應該消失。
誇誇其談將來性[Speculative Generality]
  • 若是全部裝置都會被用到,name就值得那麼作;若是用不到,就不值得。用不上的裝置只會擋住你的路,因此,把他搬開吧。
  • 若是你的某個抽象類其實沒有太大做用,請運用Collapse Hierarchy。
  • 沒必要要的委託可運用Inline Class除掉。
  • 若是某些函數參數未被使用,可使用Remove Parameter。
  • 若是函數名稱帶有多餘的抽象意味,應該對他實施Rename Method,讓它現實一些。
  • 若是函數或類的惟一用戶是測試用例,能夠丟棄。但若是它們的用途是幫助測試用例檢測正當功能,固然必須刀下留人。
使人迷惑的暫時字段[Temporary Field]
有時會有這樣的對象:其內某個實例變量僅爲某種特定狀況而設。這代碼使人不易理解,由於你一般認爲對象在有所時候都須要它的全部變量。
  • 將這些只在使用該算法時纔有效,其餘狀況下只會讓人迷惑,可使用Extract Class將這些變量和其相關函數提煉到一個獨立類中,提煉後的新對象將是一個函數對象。
過分耦合的消息鏈[Message Chains]
若是你看到用戶向一個對象請求另外一個對象,而後再向後者請求一個對象,而後再請求另外一個對象。。。這就是消息鏈。實際代碼中你可能會看到一長串的getThis()或者一長串臨時變量。採起這種方式,意味客戶代碼將於查找過程當中的導航結構緊密耦合。一旦對象間的關係發生任何變化,客戶端就不得不作出相應修改。
  • 使用Hide Delegate。你能夠在消息鏈的不一樣位置進行這種重構手法。
  • 先觀察消息鏈最終對象獲得的對象是用來幹什麼的,看看可否以Extract Method把使用該對象的代碼提煉到另外一個獨立函數中,再運用Move Method把這個函數推入消息鏈。
  • 若是這個消息鏈上的某個對象有多位客戶端航行此航線的剩餘部分,就加一個函數來作這件事。
中間人[Middle Man]
看到某個類接口有一半的函數都委託給其餘類,這樣就是過分運用。
狎暱關係[Inappropriate Intimacy]
兩個類過於親密。過於狎暱的類必須拆散,要嚴守清規。
  • 使用Move Method和Move Field幫他們劃清界限,減小狎暱行徑。
  • 運用Change Bidirectional Association to Unidirectional 讓其中一個類對另外一個類斬斷情絲。
  • 運用Extract Class把二者共同點提煉到一個安全點,讓他們坦蕩地使用這個新類。
  • 運用Hide Delegate讓另外一個類來爲他們傳遞相思情。
殊途同歸的類[Alternative Classed with Different Interfaces]
若是兩個函數作同一件事,卻有着不一樣的簽名,運用Rename Method根據它們的用途從新命名。但這每每不夠,請反覆運用Move Method將某些行爲移入類,直到二者的協議一致爲止。若是必須重複而贅餘地移入代碼才能完成,運用Extract Superclass爲本身贖點罪。
不完美的庫類[Incomplete Library Class]
  • 若是你只想修改庫類的一兩個函數,能夠運用Introduce Foreign Method。
  • 若是想添加一大推額外的行爲,能夠運用Introduce Local Extension。
純稚的數據類[Data Class]
所謂Data Class是,它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。
  • 運用Encapsulate Field將它們封裝起來。
  • 運用Encapsulate Collection把它們封裝起來。
  • 對於不被其餘類修改的字段,運用Remove Setting Method。
  • 找出取值/設置函數被其餘類運用到的地點,以Move Method把那些調用行爲搬移到Data Class中。
  • 沒法搬移的函數,運用Extract Method產生一個可被搬移的函數。
被拒絕的遺贈[Refused Bequest]
子類應該繼承超類的函數和數據。若是它們不想或不須要繼承,又該怎麼辦?它們獲得了全部禮物,卻只挑幾樣。按傳統說法,意味着繼承體系設計錯誤。
  • 你須要爲這個子類新建一個兄弟類,再運用Push Down Method和Push Down Field把全部用不到的函數下推給那個兄弟。這樣一來,超類就只持有全部子類共享的東西。你常會聽到這樣的建議:全部超類都應該是抽象的。
  • 若是子類複用了超類的行爲(實現),卻又不肯意支持超類的接口。運用Replace Inheritance with Delegation來達到目的。
過多的註釋[Comments]
當你感受須要撰寫註釋時,請先嚐試重構,試着讓全部註釋都是變得多餘。

若是你不知道該作什麼,這纔是註釋的良好運用時機。除了用來記述未來的打算以外,註釋還能夠用來標記你並沒有十足把握的區域。你能夠在註釋裏面寫下本身「爲何作某某事」。這類信息能夠幫助未來的維護者,尤爲那些健忘的傢伙。

  • 若是你須要註釋來解釋一塊代碼作了什麼,試試Extract Method。
  • 若是函數已經提煉出來,但仍是須要註釋來解析其行爲,試試Rename Method。
  • 若是你須要註釋說明某些系統的需求規格,試試Introduce Assertion。

第四章構築測試體系

頻繁地運行測試。每次編譯請把測試考慮進去-天天至少執行每一個測試一次。
每當你收到bug報告,請先寫一個單元測試來暴露bug。
編寫未萬重山的測試並實行運行,好過對完美測試的無盡等待。
考慮可能出錯的邊界條件,把測試火力集中在那。
  • 「尋找邊界條件」,包括尋找特殊的、可能致使測試失敗的狀況。對於文件相關測試,空文件是個不錯的邊界條件。
當事情被認爲應該出錯時,別忘了檢查是否跑出了預期的異常。
幾條測試規則建議
  • 當測試數量達到必定程度以後,繼續增長測試帶來的效益就會呈現遞減態勢,而非持續遞增。
  • 若是試圖編寫太多測試,你也可能由於工做量太大而氣餒,最後什麼都寫不成。
  • 你應該把測試集中在可能出錯的地方。
  • 觀察代碼,看哪兒變得複雜;觀察函數,思考那些地方可能出錯。
  • 是的,你的測試不可能找出全部bug,但一旦進行重構,你能夠更好地理解整個程序,從而找到更多bug。
不要由於測試沒法捕捉全部bug就不寫測試,由於測試的確能夠捕捉到大多數bug。
  • 測試代碼和產品代碼之間的區別:你能夠放心地複製、編寫測試代碼。

第五章 重構列表

重構的記錄格式
  1. 首先是名稱。
  2. 名稱以後是一個簡短概要。
  3. 動機爲你介紹「爲何須要這個重構」和「什麼狀況下不應使用這個重構」。
  4. 作法簡明扼要地一步一步介紹如何進行此一重構。
  5. 範例以一個十分簡單的例子說明此重構手法如何運行。

第六章 從新組織函數

Extract Method[提煉函數]
將這段代碼放入一個獨立函數中,並讓函數名解釋該函數的用途。
動機、好處
  • 簡短命名良好的函數粒度都很小,那麼函數被複用的機會就更大。
  • 這會使高層函數讀起來就像一系列的解釋。
  • 若是函數都是細粒度,那麼函數的複寫也會更容易些。
作法
  • 創造一個新函數,根據這個函數的意圖來對它命名(以它「作什麼」來命名,而不是它「怎麼樣作」命名)。
  • 將提煉出來的代碼從源函數複製到新建的目標函數中。
  • 仔細檢查提煉出來的代碼,看看其中是否引用了「做用域限於源函數」的變量。
  • 檢查是否有「僅用於被提煉代碼段」的臨時變量。若是有,在目標函數中將他們聲明爲臨時變量。
  • 檢查被提煉代碼段,看看是否有任何局部變量的值被它改變。
  • 將被體力啊代碼段中須要讀取的局部變量,看成參數傳給目標函數。
  • 處理完全部局部變量以後,進行編譯。
  • 在源函數中,將被提煉代碼段替換爲目標函數的調用。
  • 編譯,測試。
若是須要返回的變量不止一個
  • 挑選另外一塊代碼來提煉。
  • 讓每一個函數都只返回一個值。
  • 安排多個函數,用以返回多個值。
Inline Method [內聯函數]
在函數調用點插入函數本體,而後移除該函數
int getRating(){ return (moreThanFiveLateDeliveries()) ? 2 : 1 ; } boolean moreThanFiveLateDeliveries(){ return _numberOfLateDeliveries > 5; } 
  • 變成
int getRating(){ return (_numberOfLateDeliveries > 5) ? 2 : 1 ; } 
動機
  • 使代碼更清晰易讀。
  • 手上有一羣組織不合理的函數,能夠將它們都內聯到一個大型函數中,再從中提煉出組織合理的小函數。
作法
  • 檢查函數,肯定它不具多態性。
  • 找出這個函數的全部調用點。
  • 將這個函數的全部調用點都替換爲函數本體。
  • 編譯,測試。
  • 刪除該函數的定義。
Inline Temp [內聯臨時變量]
場景
  • 你有一個臨時變量,只被一個簡單表達式賦值一次,而它妨礙了其它重構手法。
將全部對該變量的引用動做,替換爲對它賦值的那個表達式自身。
double basePrice = anOrder.basePrice(); return (basePrice > 100) 
  • 變成
return (anOrder.basePrice() > 100) 
動機
  • 你發現某個臨時變量被賦予某個函數調用的返回值。
作法
  • 檢查給臨時變量賦值的語句,確保等號右邊的表達式沒有反作用。
  • 若是這個臨時變量並未被聲明爲final,那就將它聲明爲final,而後編譯。
  • 找到該臨時變量的全部引用點,將它們替換爲「爲臨時變量賦值」的表達式。
  • 每次修改後,編譯並測試。
  • 修改完全部引用點以後,刪除該臨時變量的聲明和賦值語句。
  • 編譯,測試。
Replace Temp with Query [以查詢取代臨時變量]
場景
  • 你的程序以一個臨時變量保存某一個表達式的運行結果。
將這個表達式提煉到一個獨立函數中。將這個臨時變量的全部引用點替換爲新函數的調用。此後,新函數就能夠被其餘函數使用。
double basePrice = _quantity * _itemPrice; if (basePrice > 100) return basePrice * 0.95; else return basePrice * 0.98; 
  • 變成
if(basePrice() > 100) return basePrice() * 0.95; else return basePrice() * 0.98; 
動機
  • 臨時變量的問題在於:它們是暫時的,並且只能在所屬函數內使用。因爲臨時變量只在所屬函數內可見,因此它們只會驅使你寫出更長的函數,由於只有這樣你才能訪問到須要的臨時變量。
  • 若是把臨時變量替換成一個查詢,那麼同一個類中的全部函數都將能夠得到這份信息。
  • Relace Temp with Query每每是你運用Extract Method以前必不可少的一個步驟。局部變量會使代碼難以被提煉,因此儘量把它們替換爲查詢方式。
  • 臨時變量只被複製一次,或者複製給臨時變量的表達式不受其餘條件影響。
作法
  • 找出只被賦值一次的臨時變量。
  • 將該臨時變量生命爲final。
  • 編譯。
  • 將「對該臨時變量賦值」之語句的等號右側部分提煉到一個獨立函數中。
  • 編譯,測試。
  • 在該臨時變量身上實施Inline Temp。

咱們經常使用臨時變量保存循環中的累加信息。在這種狀況下,整個循環均可以被提煉爲一個獨立函數,這也使本來的函數能夠減小掉幾行擾人的循環邏輯。

Introduce Explaining Variable[引入解釋性變量]
場景
  • 一個複雜的表達式。
將該複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。
if ((platform.toUpperCase().indexOf("MAC") > -1 ) && (browser.toUpperCase().indexOf("IE") > -1)) { do something } 
  • 變成
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; if ( isMacOs && isIEBrowser) { do something } 
動機
  • 表達式有可能很是複雜而難以閱讀。這種狀況啊下,臨時變量能夠幫助你講表達式分解爲比較容易管理的形式。
  • 將每一個條件子句提煉出來,以一個良好命名的臨時變量來解釋對應條件子句的意義。
  • 在較長的算法中,能夠運用臨時變量來解釋每一步運算的意義。
作法
  • 聲明一個final臨時變量,將待分解之複雜表達式中的一部分動做運算結果複製給它。
  • 將表達式中「運算結果」這一部分,替換爲上述臨時變量。
  • 編譯,測試。
  • 重複上述過程,處理表達式其餘部分。
Split Temporary Variable[分解臨時變量]
場景
  • 你的程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不被用於收集計算結果。
針對每次賦值,創造一個獨立、對應的臨時變量。
double temp = 2 * (_height + _width); System.out.print(temp); temp = _height * _width; System.out.print(temp); 
  • 變成
final double perimeter = 2 * (_height + _width);
System.out.print(perimeter); final double area = _height * _width; System.out.print(area); 
動機
  • 臨時變量應該只被賦值一次。若是他們被賦值超過一次,就意味着它們在函數中承擔了一個以上的責任。
  • 若是臨時變量承擔多個責任,它就應該被替換(分解)爲多個臨時變量,每一個變量只承擔一個責任。
  • 同一個臨時變量承擔兩件不一樣的事情,會令代碼閱讀者糊塗。
作法
  • 在待分解臨時變量的聲明及第一次被賦值處,修改其名稱。
  • 將新的臨時變量聲明爲final。
  • 以該臨時變量的第二次賦值動做爲界,修改此前對該臨時變量的全部引用點,讓它們引用新的臨時變量。
  • 在第二次賦值處,從新聲明原先那個臨時變量。
  • 編譯,測試。
  • 逐次重複上述過程。
Remove Assignments to Parameters[移除對參數的賦值]
場景
  • 代碼對一個參數進行賦值
以一個臨時變量取代該參數的位置。
int discount (int inputVal, int quantity, int yearToData) { if (inputVal > 50) inputVal -=2; } 
  • 變成
int discount (int inputVal, int quantity, int yearToData) { result = inputVal; if (inputVal > 50) result -=2; } 
作法
  • 創建一個臨時變量,把待處理的參數賦予它。
  • 以「對參數的賦值」爲界,將其後全部對此參數的引用點,所有替換爲「對此臨時變量的引用」。
  • 修改賦值語句,使其改成對新建之臨時變量賦值。
  • 編譯,測試。

還能夠爲參數加上關鍵詞final,從而強制它遵循「不對參數賦值」這一慣例。

Replace Method with Method Object[以函數對象取代函數]
場景
  • 你有一個大型函數,其中對局部變量的利用使你沒法採用Extract Method。
將這個函數放進一個單獨對象中,如此一來局部變量就成了對象內的字段。而後你能夠在同一個對象將這個大型函數分解爲多個小型函數。
動機
  • 將全部局部變量變成函數對象的字段,而後你就能夠對這個新對象使用Extract Method創造出新函數,從而將本來的大函數拆解變短。
作法
  • 創建一個新類,根據待處理函數的用途,爲這個類命名。
  • 在新類中創建一個final字段,用以保存原先大型函數所在對象。咱們將這個字段稱爲「源對象」。同時,針對原函數的每一個臨時變量和每一個參數在新類中創建一個對應的字段保存之。
  • 在新類中創建一個構造函數,接收源對象及原函數的全部參數做爲參數。
  • 在新類中創建一個compute()函數。
  • 將原函數的代碼複製到compute()函數中。若是須要調用源對象的任何函數,請經過源對象字段調用。
  • 編譯。
  • 將舊函數的函數本體替換爲這樣一條語句:「建立上述新類的一個新對象,然後調用其中的compute()函數」。
Substitute Algorithm[替換算法]
場景
  • 你想要把某個算法替換爲另外個更清晰的算法。
將函數本體替換爲另外一個算法。
String foundPerson (String[] person) { for (int i = 1; i < person.length; i++) { if (people[i].equals("Don")) { return "Don"; } if (people[i].equals("John")) { return "John"; } if (people[i].equals("Kent")) { return "Kent"; } } return ""; } 
  • 變成
String foundPerson (String[] people) { List candidates = Arrays.asList(new String[] {"Don", "John", "Kent"}); for (int i = 1; i < people.length; i++) { if (candidates.contains(people[i])) return people[i]; } return ""; } 
動機
  • 使用這項重構手法以前,請先肯定本身已經儘量分解了原先函數。替換一個巨大而複雜的算法是很是困難的,只有先將它分解爲較爲簡單的小型函數。
作法
  • 準備好另外一個算法,讓它經過變異。
  • 針對現有測試,執行上述的新算法。若是結果與本來結果相同,重構結束。
  • 古國測試結果不一樣於原先,在測試和調試過程當中,以舊算法爲比較參數標準。

第7章 在對象之間搬移特性

Move Method[搬移函數]
場景
  • 你的程序中,有個函數與其所駐類以外的另外一個類進行更多交流:調用後者,或被後者調用。
在該函數最常引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或是將舊函數徹底移除。
動機
  • 若是一個類有太多行爲,或若是一個類與另外一個類有太多合做而造成高度耦合,我就會搬移函數。
  • 經過這種手段,可使系統中的類更簡單,這些類最終也將更乾淨利落地實現系統交付的任務。
作法
  • 檢查源類中被源函數所使用的一切特性,考慮它們是否也該被搬移。
  • 檢查源類的子類和超類,看看是否有該函數的其餘聲明。
  • 在目標類中聲明這個函數。
  • 將源函數的代碼複製到目標函數中。調整後者,使其能在新家中正常運行。
  • 編譯目標類。
  • 決定如何從源函數正確引用目標對象。
  • 修改源函數,使之成爲一個純委託函數。
  • 編譯,測試。
  • 決定是否刪除源函數,或將它當作一個委託函數保留下來。
  • 若是要移除源函數,請將源類中對源函數的全部調用,替換爲目標函數的調用。
  • 編譯,測試。
Move Field[搬移字段]
場景
  • 你的程序中,某個字段將其所駐類以外的另外一個類更多地用到。
在目標類新建一個字段,修改源字段的全部用戶,令它們改用新字段。
動機
  • 若是發現一個字段,在其所駐類以外的另外一個類中有更多函數使用了它,我就會考慮搬移這個字段。
作法
  • 若是字段的訪問級別是public,使用Encapsulate Field將它封裝起來。
  • 編譯,測試。
  • 在目標類中創建與源字段相同的字段,並同時創建相應的設置/取值函數。
  • 編譯目標類。
  • 決定如何在源對象中引用目標對象。
  • 刪除源字段。
  • 將全部對源字段的引用替換爲對某個目標函數的調用。
  • 編譯,測試。
Extract Class[提煉類]
場景
  • 某個類作了應該由兩個類作的事。
創建一個新類,將相關的字段和函數從舊類搬移到新類。
動機
  • 一個類應該是一個清楚的抽象,處理一些明確地責任。
  • 另外一個每每在開發後期出現的信號是類的子類化方式。
動機
  • 決定如何分解類所負的責任。
  • 創建一個新類,用以表現從舊類中分離出來的責任。
  • 創建「從舊類訪問新類」的鏈接關係。
  • 對於你想搬移的每個字段,運動Move Field搬移之。
  • 每次搬移後,編譯,測試。
  • 使用Move Method將必要函數搬移到新類。先搬移較底層函數(也就是「被其餘函數調用」多於「調用其餘函數」),在搬移較高層函數。
  • 每次搬移以後,編譯,測試。
  • 檢查,精簡每一個類的接口。
  • 決定是否公開新類。若是你的確須要公開它,就要決定讓它成爲引用對象仍是不可變的值對象。
Inline Class[將類內聯化]
場景
  • 某個類沒有作太多事情。
將這個類的全部特性搬移到另外一個類中,而後移除原類。
動機
  • Inline Class 與 Extract Class 相反。
  • 若是一個類再也不承擔足夠責任,再也不有單獨存在的理由,我就會挑選一個「萎縮類」的最頻繁用戶,以Inline Class 手法將「萎縮類」塞進另外一個類中。
作法
  • 在目標類身上聲明源類的public協議,並將其中全部函數委託至源類。
  • 修改全部源類引用點,改而引用目標類。
  • 編譯,測試。
  • 運用Move Method和Move Field,將源類的特性所有搬移到目標類。
  • 爲源類舉行一個簡單的「喪禮」。
Hide Delegate[隱藏「委託關係」]
場景
  • 客戶經過一個委託類來調用另外一個對象。
在服務類創建客戶所需的全部函數,用以隱藏委託關係。
動機
  • 「封裝」意味每一個對象都應該儘量少了解系統的其餘部分。
  • 如此一來,一旦發生變化,須要瞭解這一變化的對象就會比較少--這會使變化比較容易進行。
作法
  • 對於每一個委託關係中的函數,在服務對象端創建一個簡單的委託函數。
  • 調整客戶,令它只調用服務對象提供的函數。
  • 每次調整後,編譯測試。
  • 若是未來再也不有任何客戶須要用Delegate委託類,即可移除服務對象中的相關訪問函數。
Remove Middle Man[移除中間人]
場景
  • 某個類作了過多的簡單委託動做。
讓客戶直接調用受託類
作法
  • 創建一個函數,用以得到受託對象。
  • 對於每一個委託函數,在服類中刪除該函數,並讓須要調用該函數的客戶轉爲調用受託對象。
  • 處理每一個委託函數後,編譯,測試。
class Person ... Department _department; public Person getManger(){ return _department.getManger(); } class Department ... private Person _manager; public Department (Person manager){ _manager = manager; } // 客戶端調用 manager = john.getManager(); 若是有大量函數都這麼作,那麼不得不在Person之中安置大量委託行爲。 
  • 變成
class Person ... Department _department; public Person getManger(){ return _department; } class Department ... private Person _manager; public Department (Person manager){ _manager = manager; } // 客戶端調用 manager = john.getManager().getManager(); 
Introduce Foreign Method[引入外加函數]
場景
  • 你須要爲提供服務的類增長一個函數,但你沒法修改這個類。
在客戶類中創建一個函數,並以第一參數形式傳入一個服務類實例。
作法
  • 在客戶類中創建一個函數,用來提供你須要的功能。
  • 以服務類實例做爲該函數的第一個參數。
  • 將該函數註釋爲「外加函數,應在服務類中實現」。
Introduce Local Extension[引入本地拓展]
場景
  • 你須要爲服務類提供一些額外函數,但你沒法修改這個類。
創建一個新類,使它包含這些額外函數。讓這個擴展品稱爲源類的子類或包裝類。
作法
  • 創建一個擴展類,將它做爲原始類的子類或包裝類。
  • 在擴展類中加入轉型構造函數。
  • 在擴展類中加入新特性。
  • 根據須要,將原對象替換爲擴展對象。將針對原始類定義的全部外加函數搬移到擴展類中。

第八章 從新組織數據

魔法數
  • 也就是帶有特殊含義的數字。
Self Encapsulate Field[自封裝字段]
場景
  • 你直接訪問一個字段,但與字段之間的耦合關係逐漸變得笨拙。
爲這個字段創建取值/設值函數,而且只以這些函數來訪問字段。
private int _low, _high; boolean includes (int arg) { return arg >= _low && arg <= _high; } 
  • 變成
private int _low, high; boolean includes (int arg) { return arg >= getLow() && arg <= getHigh(); } int getLow () {return _low;} int getHigh () {return _high;}; 
動機
  • 「字段訪問方式」這個問題上,兩種大相徑庭的觀點:
    • 在該變量定義所在的類中,你能夠自由訪問它。
    • 即便在這個類中你也應該只使用訪問函數間接訪問。
  • 間接訪問變量的好處:
    • 子類能夠經過腹瀉一個函數而改變獲取數據的途徑。
    • 它還支持更靈活的數據管理方式,例如延遲初始化(意思是:只有在須要用到某值時,纔對它初始化)。
    • 直接訪問變量的好處:
    • 代碼比較容易閱讀。
作法
  • 爲待封裝字段創建取值/設值函數。
  • 找出該字段的全部引用點,將他們所有改成調用取值/設值函數。
  • 將該字段聲明爲private。
  • 複查,確保找出全部引用點。
  • 編譯,測試。
Replace Data Value with Object[以對象取代數據值]
場景
  • 你有一個數據項,須要與其餘數據和行爲一塊兒使用纔有意義。
將數據項變成對象。
作法
  • 爲待替換數值新建一個類,在其中生命一個final字段,其類型和源類中的待替換數值類型同樣。而後在新類中加入這個字段的取值函數,再加上一個接受此字段爲參數的構造函數。
  • 編譯。
  • 將源類中的待替換數值字段的類型改成前面新建的類。
  • 修改源類中該字段的取值函數,令它調用新類的取值函數。
  • 若是源類構造函數中用到這個待替換字段(多半是賦值動做),咱們就修改構造函數,令它改用新類的構造函數來對字段進行賦值動做。
  • 修改源類中待替換字段的設置函數,令它爲新類建立一個實例。
  • 編譯,測試。
  • 如今,你有能耐須要對新類使用Change Value to Reference。
Change Value to Reference[將值對象改成引用對象]
場景
  • 你從一個雷衍生出許多彼此相等的實例,但願將它們替換爲同一個對象。
作法
  • 使用Replace Constructor with Factory Method。
  • 編譯,測試。
  • 決定由什麼對象負責提供訪問新對象的途徑。
  • 決定這些引用對象應該預先建立好,或是應該動態建立。
  • 修改工廠函數,令它返回引用對象。
  • 編譯,測試。
Change Reference to Value[將引用對象改成值對象]
場景
  • 你有一個引用對象,很小且不可變,並且不易管理。
作法
  • 檢查重構目標是否爲不可變對象,或是否可修改成不可變對象。
  • 創建equals()和hashCode()。
  • 編譯,測試。
  • 考慮是否能夠刪除工廠函數,並將構造函數聲明爲public。
Replace Array with Object[以對象取代數組]
場景
  • 你有一個數組,其中的元素各自表明不一樣的東西。
以對象替換數組。對於數組中的每一個元素,以一個字段來表示。
String[] row = new String[3]; row[0] = "Liverpool"; row[1] = "15"; 
  • 變成
Performance row = new Performance(); row.setName("Liverpool"); row.setWins("15"); 
作法
  • 新建一個類表示數組所擁有的信息,並在其中以一個public字段保存原先的數組。
  • 修改數組的全部用戶,讓它們改用新類的實例。
  • 編譯,測試。
  • 逐一爲數組元素添加取值/設值函數。根據元素的用途,爲這些訪問函數命名。修改客戶端代碼,讓它們經過訪問函數取用數組內的元素。每次修改後,編譯並測試。
  • 當全部對數組的直接訪問都轉而調用訪問函數後,將新類中保存該數組的字段聲明爲private。
  • 編譯。
  • 對於數組內的每個元素,在新類中建立一個類型至關的字段。修改該元素的訪問函數,令它改成上述的新建字段。
  • 每修改一個元素,編譯並測試。
  • 數組的全部元素都有了相應字段以後,刪除該數組。
Duplicate Observed Data[複製「被監視數據」]
場景
  • 你有一些領域數據置身於GUI控件中,而領域函數須要訪問這些數據。
將該數據複製到一個領域對象中。創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據。
動機
  • 一個分層良好的系統,應該將處理用戶和處理業務邏輯的代碼分開。之因此這樣作,是:
    • 你可能須要使用不一樣的用戶界面來表現相同的業務邏輯,若是同時承擔兩種責任,用戶界面會變得過度複雜。
    • 與GUI隔離以後,領域對象的維護和演化都會更容易,你甚至可讓不一樣的開發者負責不一樣部分的開發。
作法
  • 修改展現類,使其成爲領域類的Observer。
  • 針對GUI類的領域數據,使用Self Encapsulate Field。
  • 編譯,測試。
  • 在事件處理函數中調用設值函數,直接更新GUI組件。
  • 編譯,測試。
  • 在領域類中定義數據及其相關訪問函數。
  • 修改展現類的訪問函數,將它們的操做對象改成領域對象。
  • 修改Observer的update(),使其從相應的領域對象中將所需數據複製給GUI組件。
  • 編譯,測試。
Change Unidirectional Association to Bidirectional[將單向關聯改成雙向關聯] TODO
場景
  • 兩個類都須要使用對方特性,但其間只有一條單向鏈接。
添加一個反向指針,並使修改函數可以同時更新兩條鏈接。
作法
  • 在被引用類中增長一個字段,用以保存反向指針。
  • 決定由哪一個類--引用端仍是被引用端--控制關聯關係。
  • 在被控端創建一個輔助函數,其命名應該清楚指出它的有限用途。
  • 若是既有的修改函數在控制端,讓它負責更新反向指針。
  • 若是既有的修改函數在被控端,就在控制端創建一個控制函數,並讓既有的修改函數調用這個新建的控制函數。
Change Bidirectional Association to Unidirectional[將雙向關聯改成單向關聯] TODO
場景
  • 兩個類之間有雙向關聯,但其中一個類現在再也不須要另外一個類的特性。
去除沒必要要的關聯。
Replace Magic Number with Symbolic Constant[以字面常量取代魔法數]
場景
  • 你有一個字面數值,帶有特別的函數。
建立一個常量,根據其意義爲它命名,並將上述的字面數值替換爲這個常量。
double testArea (double high) { return 3.14 * high; } 
  • 變成
double testArea (double high) { return WITH_CONSTANT * high; } static final double WITH_CONSTANT; 
Encapsulate Field[封裝字段]
場景
  • 你的類中存在一個public字段。
將它聲明爲private,並提供相應的訪問函數。
public String _name; 
  • 變成
private String _name; public String getName () { return _name; } public function void setName (String arg) { _name = arg; } 
Encapsulate Collection[封裝集合]
場景
  • 有個函數返回一個集合。
讓這個函數返回集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數。
作法
  • 加入爲集合添加/移除元素的函數。
  • 將保存集合的字段初始化爲一個空集合。
  • 編譯。
  • 找出集合設置函數的全部調用者。你能夠修改那個設值函數,讓它使用上述新創建的「添加/移除元素」函數;也能夠直接修改調用端,改讓它們調用上述新創建的「添加/移除元素」函數。
  • 編譯,測試。
  • 找出全部「經過取值函數得到集合並修改其內容」的函數。逐一修改這些函數,讓它們改用添加/移除函數。每次修改後,編譯,測試。
  • 修改完上述全部「經過取值函數得到集合並修改集合內容」的函數後,修改取值函數自身,使它返回該集合的一個只讀副本。
  • 編譯,測試。
  • 找出取值函數的全部用戶,從中找出應該存在於集合所屬對象內的代碼。運用Extract Method 和 Move Method 將這些代碼移到宿主對象中。
  • 修改現有取值函數的名字,而後添加一個新取值函數,使其返回一個枚舉。找出就取值函數的全部被用點,將他們都改成新取值函數。
  • 編譯,測試。
String[] getSkills() { return _skills; } void setSkills (String[] arg) { _skills = arg; } String [] _skills; 
  • 變成
void setSkill(int index, String newSkill) { _skills[index] = newSkill; } String[] getSkills() { return _skills; } 
Replace Record with Data Class[以數據類取代記錄]
場景
  • 你須要面對傳統變成環境中的記錄結構。
爲該記錄建立一個「啞」數據對象。
Replace Type Code with Class[以類取代類型碼]
場景
  • 類之中有一個類值類型碼,但它並不影響類的行爲。
以一個新類替換該數值類型碼。
Replace Type Code with Subclasses[以子類取代類型碼]
場景
  • 你有一個不可變的類型碼,它會影響類的行爲。
以子類取代這個類型碼。
作法
  • 使用Self Encapsulate Field 將類型碼自我封裝起來。
  • 爲類型碼每個數值創建一個相應的子類。在每一個子類中腹瀉類型碼的取值函數,使其返回相應的類型碼值。
  • 每創建一個新的子類,編譯,測試。
  • 從超類中刪除保存類型碼的字段。將類型碼訪問函數聲明爲抽象函數。
  • 編譯,測試。
Replace Type Code with State/Strategy[以State/Strategy取代類型碼]
場景
  • 你有一個類型碼,它會影響類的行爲,但你沒法經過繼承手法消除它。
以狀態對象取代類型碼。
Replace Subclass with Fields[以字段取代子類]
場景
  • 你的各個子類的惟一差異只在「返回常量數據」的函數身上。
修改這些函數,使它們返回超類中的某個新增字段,而後銷燬子類。

第九章 簡化條件表達式

Decompose Conditional[分解條件表達式]
場景
  • 你有一個複雜的條件(if-then-else)語句。
從if、then、else三個段落中分別提取出獨立函數。
if (date.before (SUMMER_STATE) || date.after(SUMMER_END)) { charge = quantity * _winterRate + _winterServiceChange; }else { charge = quantity * _summerRate; } 
  • 變成
if (notSummer(date)) { charge = winterCharge(quantity); }else { charge = summerCharge(quantity); } 
Consolidate Conditional Expression[合併條件表達式]
場景
  • 你有一系列條件測試,都獲得相同結果。
將這些測試合併爲一個條件表達式,將這個條件表達式提煉成爲一個獨立函數。
範例:使用邏輯或
double disabilityAmount() {
    if (_seniority < 2) return 0; if (_monthsDisabled > 12) return 0; if (_isPartTime) return 0; } 
  • 變成
double disabilityAmount() { if (isNotEligableForDisability()) return 0; } boolean isNotEligableForDisability() { return ((_seniority < 2) || (_monthsDisabled > 12) || (_isPartTime)); } 
範例:使用邏輯與
if (onVacation()) if (lengthOfService() > 10) return 1; return 0.5; 
  • 變成
if (onVacation() && lengthOfService > 10 ) return 1; else return 0.5; 
  • 使用三元操做變成
return (onVacation() && lengthOfService > 10 ) ? 1 : 0.5; 
Consolidate Duplicate Conditional Fragments[合併重複的條件片斷]
場景
  • 在條件表達式的每一個分支上有着相同的一段代碼。
將這段重複代碼移到條件表達式以外。
if (isSpecialDeal()) {
    total = price * 0.95; send(); } else { total = price * 0.95; send(); } 
  • 變成
if (isSpecialDeal()) total = price * 0.95; else total = price * 0.98; send(); 
Remove Control Flag[移除控制標記]
場景
  • 在一系列布爾表達式中,某個變量帶有「控制標記」的做用。
以break語句或return語句取代控制標記。
Replace Nested Conditional with Guard Clauses[以衛語句取代嵌套條件表達式]
場景
  • 函數中的條件邏輯令人難以看清正常執行路徑。
使用衛語句表現全部特殊狀況
double getPayAmount() {
    double result; if (_isDead) result = deadAmount(); else { if (_isSeparated) result = separatedAmount(); else { if (_isRetired) result = retiredAmount(); else result = normalPayAmount(); } }  return result; } 
  • 變成
double getPayAmount() { if (_isDead) return deadAmount(); if (_isSeparated) return separatedAmount(); if (_isRetired) return retiredAmount(); return normalPayAmount(); } 
Replace Conditional with Polymorphism[以多態取代條件表達式]
場景
  • 你手上有個表達式,它根據對象類型的不一樣而選擇不一樣的行爲。
將這個條件表達式的每一個分之放進一個子類內的覆寫函數中,而後將原始函數聲明爲抽象函數。
Introduce Null Object[引入Null對象]
場景
  • 你須要再三檢查某對象是否爲null。
將null值替換爲null對象
if (customer == null) plan = BillingPlan.basic(); else plan = customer.getPlan(); 

第十章 簡化函數調用

Rename Method[函數更名]
場景
  • 函數的名稱未能揭示其函數的用途。
修改函數名稱。
getinvcdtlmt() {} 
  • 變成
getInvoiceableCreditLimit() {} 
動機
  • 給函數命名有一個好辦法:首先考慮應該給這個函數寫上一句怎樣的註釋,而後想辦法將註釋變成名稱。
Add Prameter[添加參數]
場景
  • 某個函數須要從調用端獲得更多信息。
爲此函數添加一個對象參數,讓該對象待近函數所需信息。
Remove Parameter[移除參數]
場景
  • 函數本體再也不須要某個參數。
將該參數移除。
Separate Query from Modifier[將查詢函數和修改函數分離]
場景
  • 某個函數既返回對象狀態值,又修改對象狀態。
創建兩個不一樣的函數,其中一個負責查詢,另外一個負責修改。
Parameterize Method[令函數攜帶參數]
場景
  • 若干函數作了相似的工做,但在函數本體中卻包含了不一樣值。
創建單一函數,以參數表達那些不一樣值。
void tenPercentRaise() { salary *= 1.1; } void fivePercentRaise() { salary *= 1.05; } 
  • 變成
void raise(double factor) { salary *= (1+factor); } 
Replace Parameter with Explicit Methods[以明確函數取代參數]
場景
  • 你有一個函數,其中徹底取決於參數值的不一樣行爲。
針對該參數的每個可能值,創建一個獨立函數。
void setValue(String name, int value) { if (name.equals("height")) { _height = value; return; } if (name.equals("width")) { _width = value; return; } Assert.shouldNerverReachHere(); } 
  • 變成
void setHeight(int arg) { _height = arg; } void setWidth(int arg) { _width = arg; } 
Preserve Whole Object[保持對象完整]
場景
  • 你從某個對象中取出若干值,將他們做爲某一此函數調用時的參數。
改成傳遞整個對象。
int low= daysTempRange().getLow(); int hiht = daysTempRange().getHigh(); withinPlan = plan.withinRange(low, high); 
  • 變成
withinPlan = plan.withinRange(daysTempRange()); 
Replaace Parameter with Methods[以函數取代參數]
場景
  • 對象調用某個函數,並將所得結果做爲參數,傳遞給另外一個函數。而接受該參數的函數自己也可以調用前一個函數。
讓參數接受者去除該項參數,並直接調用前一個函數。
int basePrice = _quantity * _itemPrice; discountLevel = getDiscountLevel(); double finalPrice = discountedPrice (basePrice, discountLevel); 
  • 變成[若是函數能夠經過其餘途徑得到參數值,name它就不該該經過參數取得該值。]
int basePrice = _quantity * _itemPrice; double finalPrice = discountedPrice (basePrice, discountLevel); 
Introduce Parameter Object[引入參數對象]
場景
  • 某些參數老是很天然地同時出現。
以一個對象取代這些參數。
amountInvoicedln(start: Date, end:Date); amountReceiveln(start: Date, end:Date); amountOverdyeln(start: Date, end:Date); 
  • 變成
amountInvoicedln(DateRange); amountReceiveln(DateRange); amountOverdyeln(DateRange); 
Remove Setting Method[移除設置函數]
場景
  • 類中的某個字段應該在對象建立時被設值,而後就再也不改變。
去掉該字段的全部設值函數。
class Account { private String _id; Account(String id) { setId(id); } void setId(String arg) { _id = arg; } } 
  • 變成
class Account { private final String _id; Account(String id) { _id = $id; } } 
Hide Method[隱藏函數]
場景
  • 有一個函數,歷來沒有被其餘任何類用到。
將這個函數修改成private。
Replace Constructor with Factory Method[以工廠函數取代構造函數]
場景
  • 你但願在建立對象時不只僅是作簡單的構造動做。
將構造函數替換爲工廠函數。
Employee(int type) {
    _type = type; } 
  • 變成
static Employee create(int type) { return new Employee(type) ;} 
Replace Error Code with Exception[以異常取代錯誤碼]
場景
  • 某個函數返回一個特定的代碼,用以表示某種錯誤狀況。
改用異常處理。
int withdraw(int amount) { if (amount > _balance) return -1; else { _balance -= amount; return 0; } } 
  • 變成
void withdraw(int amount) throws BalanceException { if (amount > _balance) throw new BalanceException(); _balance -= amount; } 

第十一章 處理歸納關係

Pull Up Field[字段上移]
場景
  • 兩個子類擁有相同的字段。
將該字段移至超類。
abstract class Employee { } class SalesMan extends Employee { public $name; } class Engineer extends Employee { public $name; } 
  • 變成
abstract class Employee { public $name; } class SalesMan extends Employee { } class Engineer extends Employee { } 
Pull Up Method[函數上移]
場景
  • 有些函數,在各個子類中產生徹底相同的結果。
將該函數移至超類。
abstract class Employee { } class SalesMan extends Employee { public function getName() { } } class Engineer extends Employee { public function getName() { } } 
  • 變成
abstract class Employee { public function getName() { } } class SalesMan extends Employee { } class Engineer extends Employee { } 
Pull Up Constructor Body[構造函數本體上移]
場景
  • 你在各個子類中擁有一些構造函數,它們的本體幾乎徹底一致。
在超類中新鍵一個構造函數,並在子類構造函數中調用它。
Pull Down Method[函數下移]
場景
  • 超類中的某個函數只與部分(而非所有)子類有關。
將這個函數移到相關的那些子類中。
abstract class Employee { public function getQuota() { } } class SalesMan extends Employee { } class Engineer extends Employee { } 
  • 變成
abstract class Employee { } class SalesMan extends Employee { public function getQuota() { } } class Engineer extends Employee { } 
Push Down Field[字段下移]
場景
  • 超類中的某個字段只被部分(而非所有)子類用到。
將這個字段移到須要它的子類中。
Extract Subclass[提煉子類]
場景
  • 類中的某些特性只被某些(而非所有)實例用到。
新建一個子類,將上面所說的那一部分特性移到子類中。
Extract Superclass[提煉超類]
場景
  • 兩個類有類似的特性。
爲這兩個雷創建一個超類,將相同的特性移至超類。
Extract Interface[提煉接口]
場景
  • 若干客戶使用類接口中同一個子集,或兩個類的接口有部分相同。
將相同的子集提煉到一個獨立接口中。
Collapse Hierarchy[摺疊繼承體系]
場景
  • 超類和子類之間無太大區別。

####### 將他們合爲一體

塑造模板函數
場景
  • 你有一些子類,其中響應的某些函數以相同順序執行相似的操做,但各個操做的細節上有所不一樣。
將這些操做分別放進獨立函數中, 並保持他們都有相同的簽名,因而原函數也就變得相同了。而後將源函數上移至超類。
Replace Inheritance with Delegation[以委託取代繼承]
場景
  • 某個子類至使用超類接口中的一部分,或是根本不須要繼承而來的數據。
在子類中新建一個字段用以保存超類;調整子類函數,令它改而委託超類;而後去掉二者之間的繼承體系。
clas MyStack extends Vector { public void push(Object element) { 。。。。 } public Object pop() { Object result = firstElement(); removeElementAt(0); return result; } } 
  • 變成
class MyStack { private Vector _vector = new Vector(); public boolean isEmpty() { return _vector.isEmpty(); } } 
Replace Delegation with Inheritance[以繼承取代委託]
場景
  • 你在兩個類之間使用委託關係,並常常爲整個接口編寫許多極爲簡單的委託函數。
讓委託類繼承受託類。

第十二章 大型重構

Tease Apart Inheritance[梳理並分解繼承體系]
場景
  • 某個繼承體系同時承擔兩項責任。
創建兩個繼承體系,並經過委託關係讓其中一個能夠調用另外一個。
動機
  • 要指出繼承體系是否承擔了兩項不一樣的責任並不困難:若是繼承體系中的某以特定層級上的全部類,其子類名稱都以相同的形容詞開始,那麼這個體系極可能是承擔着兩項不一樣的責任。
Convert Procedural Design to Objects[將過程化設計轉化爲對象設計]
場景
  • 你手上有一些傳統過程化風格的代碼。
將數據記錄變成對象,將大塊的行爲分紅小塊,並將行爲移入相關對象之中。
Separate Domain from Presentation[將領域和表達/顯示分離]
場景
  • 某些GUI類之中包含了領域邏輯。
將領域邏輯分離出來,爲它們創建獨立的領域類。
動機
  • MVC模式最核心的價值在於:它將用戶界面代碼(即視圖;亦即現金常說的「展示層」)和領域邏輯(即模型)分離了。展示類只含用以處理用戶界面的邏輯;領域類不含任何與程序外觀相關的代碼,只含業務邏輯相關代碼。
Extract Hierarchy[提煉繼承體系]
場景
  • 你有某個類作了太多工做,其中一部分工做是一大量條件表達式完成的。
創建繼承體系,以一個子類表示一種特殊狀況。
相關文章
相關標籤/搜索