重構改善既有代碼

重構改善既有代碼

  • 第一次作某件事情的時候儘管去作,第二次作相似的事會產生反感,第三次再作相似的事,你就應該重構。
  • 小型函數優美動人
  • 一個類最好是常量類,任何的改變都是調用該類自己的接口實現。

0 壞代碼的味道

一、重複代碼

  • Duplicated Code
  • 同一類中的兩個函數含有相同的表達式,提取到方法
  • 互爲兄弟的子類含有相同表達式,將兩個子類的相同代碼提取方法推入超類
    • 若是有類似代碼,經過提煉方法將類似和差別部分分割開,並使用疏鑿模板方法,並將模板方法上移到超類中。
  • 若是兩個絕不相關的類出現重複代碼,將重複代碼提煉到一個提煉類中,兩個類都使用這個提煉類。

二、過長函數

  • Long Method
  • 間接層所能帶來的所有利益——解釋能力、共享能力、選擇能力
  • 小函數的價值是巨大的
  • 每當感受須要註釋來講明什麼的時候,就須要把說明的東西寫進一個獨立函數中,並以其用途命名。
  • 函數內有大量的臨時變量和參數。須要運用提煉方法,能夠將臨時變量做爲參數傳入,也可使用以查詢替代臨時變量,當方法參數特別多的時候能夠提煉參數類,傳遞參數類實體。若是這麼作還有不少的參數,那麼就應該用方法對象來取代方法了。
  • 選擇提煉哪一段代碼
    • 尋找註釋,有註釋的地方都在提醒你須要提煉方法了,註釋名稱就是很好的方法名
    • 條件表達式和循環也是型號,能夠用 分解條件表達式,循環應該將循環中的代碼提煉到獨立函數中。

三、過大的類

  • Large Class
  • 若是單個類作太可能是事情,每每會致使出現太多的實例變量,一旦如此,重複代碼就接踵而至了。
  • 可使用提煉類將幾個變量和方法提煉出來,若是數個變量存在着相同的前綴或字尾,就覺得着有機會能夠把它們提煉到某個組件中。若是這個組件適合一個子類,還可使用提煉子類。
  • 若是一個擁有太多代碼,能夠先肯定客戶端如何使用它們,而後運用提煉接口,爲每一種使用方法提煉出一個接口,這能夠看清楚如何分解這個類。
  • 若是超大類是一個GUI類,能夠把數據和行爲移到一個獨立的領域對象去,可能須要兩邊保留一些重複代碼,並保持兩邊同步。

四、過長的參數列

  • Long Parameter List
  • 若是向已有的對象發出一條請求就能夠取代一個參數,那麼就可使用用方法取代參數方法。
  • 還可使用保持整個對象,傳遞整個對象,
  • 提煉參數對象
  • 形成函數關聯須要慎重考慮

五、發散式變化

  • Divergent Chane
  • 軟件再怎麼說就應該是軟的,一旦須要修改,但願可以跳到系統的某一點,只在該處作修改。若是不能的化就有一種刺鼻味道了。
  • 某個類常常由於不一樣緣由在不一樣不一樣方向上發生變化發散式變化就出現了,
  • 一旦出現這種發散式變化那麼就須要將對象分解成多個對象或者會更好,當出現多個類後還能夠提煉超類等。

六、霰彈式修改

  • Shotgun Surgery
  • 正對某一項變化須要在許多不一樣類種作出須要小修改,所面臨的味道就是霰彈式修改,
  • 這種狀況應該使用移動方法和移動字段,把全部修改的代碼放進同一個類,若是沒有現存的類能夠按值這些代碼就創造一個,使用內聯類能夠將一系列相關行爲放進同一個類。
  • 這也可能形成少許的發散式變化,

七、依戀情結

  • Feature Envy
  • 對象技術的所有要點在於:這是一種將數據和對數據的操做行爲包裝在一塊兒的技術,有一中經典的氣味是:函數對某個類的興趣高於對本身所處類的興趣。
  • 使用移動方法把某些方法移動帶它該去的地方,有的時候還須要提煉方法
  • 若是某個函數須要須要幾個類的功能,判斷哪一個類擁有最多被此函數使用的數據,而後就把這個函數和那些數據擺在一塊兒,能夠先將函數分解成多個較小函數分別置於不一樣地點。
  • 將老是一塊兒變化的東西放在一塊,數據和引用這些數據的行爲老是一塊兒變化的。
  • 策略和訪問者模式能夠輕鬆修改函數行爲,付出了多一層的代價

八、數據泥團

  • Data Clumps
  • 數據項會三五成羣出現。
  • 若是刪除總舵數據中的一項,其餘數據有沒有失去意義,若是它們再也不有意義,就是一個明確的信號,應該產生一個新對象。

九、基本類型偏執

  • Primitive Obsession
  • 結構類型容許你將數據組織成有意義的形式,對象的極大價值在於打破了橫亙於基本數據和較大類之間的界限。
  • 積極的使用使用對象替換數據值,用類替換類型碼,用狀態/策略模式替代類型碼

十、swithc驚悚現身

  • Switch Statements
  • 面向對象程序的最明顯特徵就是少用switch,使用switch的問題在於重複,在修改上,若是switch散佈於不一樣地點,就要添加新的case子句
  • 若是看到switch語句的時候須要考慮用多態來替換它,問題在於多態出如今哪兒
  • 使用提煉函數將switch提煉到獨立函數中,再用移動方法將它搬移到須要多態性的類中,用子類替代類型碼或者使用state/strategy替代類型碼,完成以後再用使用多態替代條件。

十一、平行繼承體系

  • Parallel Inheritance Hierarchies
  • 若是爲某個類增長一個子類的時候必需要爲另外一類相應增長一個子類。
  • 若是某個繼承體系的類名稱前綴和兩一個繼承體系的類的名稱前綴徹底相同
  • 讓一個繼承體系的實例引用另外一個繼承體系的實例,再使用移動方法和字段,就能夠將引用端的繼承體系消除。

十二、冗贅類

  • Lazy Class
  • 建立的每一個類都有人去理解它維護它,若是一個類不值得其身價就應該消失。

1三、誇誇其談的將來性

  • Speculative Generality
  • 總有一天須要作這件事,企圖以各式各樣的勾子和特殊狀況來處理一些非必要事情會形成程序難以理解。不須要

1四、使人迷惑的暫時字段

  • Temporary Field
  • 某個實例變量僅爲某種特定狀況而設置。
  • 使用提煉類給這些孤兒創造一個家,而後把全部和這個變量相關的代碼都放進這個新家,還可使用空對象方法建立一個空對象。

1五、過分耦合的消息鏈

  • Message Chains
  • 一個對象請求一個對象,而後後者請求另外一個對象,等等
  • 使用隱藏委託。

1六、中間人

  • Middle Man
  • 對象的基本特徵之一就是封裝,對外部世界隱藏內部細節,封裝每每伴隨着委託,但有可能過分使用委託,移除中間人

1七、狎暱關係

  • Inappropriate Intimacy
  • 兩個類過於親密,移動方法和字段讓他們劃清界限。若是劃清不了就使用提煉類讓他們融爲一體吧

1八、殊途同歸類

  • Alternative Classes with Different Interfaces
  • 重命名方法,提煉子類

1九、不完美的庫類

  • Incomplete Library Class
  • 給庫類加入新的方法,外部方法和本地擴展。

20、純稚的數據類

  • Data Class
  • 不會說話的數據容器必定被其餘類過度的操控着,運用封裝字段封裝,移動設置方法,移動方法,提煉方法。

2一、被拒絕的遺贈

  • Refused Bequest
  • 子類不肯所有繼承,爲這個子類建立一個兄弟類,在運用下移方法和字段把用不到的函數下推給那個兄弟,這樣一來,超類就只持有全部子類共享的東西。
  • 用委託替換繼承

2二、過多註釋

  • Comments
  • 提煉方法。

1 從新組織函數

對函數的重構方法程序員

一、提煉函數

  • ExtractMethod
    • 1572396877904
  • 動機
    • 每一個函數的顆粒度都比較小,高層函數讀起來就像是註釋
    • 顆粒度比較小覆寫也比較容易
  • 何時須要提煉函數
    • 當函數體的語義與函數名稱偏離的時候就須要提取
  • 怎麼提取
    • 將代碼提取出來用函數的意圖來命名(作什麼)
    • 若是該代碼段中有讀取或改變臨時變量
      • 該臨時變量在原函數中有沒有使用,
        • 優先考慮用查詢取代臨時變量
        • 沒有直接將臨時變量的聲明移植到函數體中
        • 在函數體以前使用,做爲參數傳入
        • 在函數體以後使用,做爲函數返回值返回
        • 以前以後都使用,做爲參數傳入,在做爲返回值返回
    • 若是臨時變量很是多,
      • 須要考慮這個函數體是否真的屬於這個類
      • 查詢替代臨時變量

二、內聯函數

  • Inline Method
    • 1572396856168
  • 何時須要內聯
    • 當函數的本體和名稱一樣清楚易懂的時候
    • 當有一大羣組織不太合理的函數,想重構的時候,將一大羣函數內聯而後從新提取
    • 有太多的間接層,全部函數彷佛都是對另外一個函數的簡單委託
  • 怎麼內聯
    • 檢查函數,肯定它不具備多態。
    • 找出該函數的全部引用點,用函數體替換(最好用文本查找的方式找)

三、內聯臨時變量

  • Inline Temp
    • 1572396842515
  • 動機
  • 何時作
    • 有一個臨時變量,只被簡單表達式賦值一次,而它妨礙其餘重構手法
  • 怎麼作

四、以查詢取代臨時變量*

  • Replace Temp with Query
    • 1572396817819
  • 動機
    • 臨時變量是暫時的,若是這個臨時變量須要被使用屢次就考慮須要用查詢取代,這邊的查詢能夠直接使用.net中的屬性。
    • 臨時變量會驅使函數變長,若是變成查詢,類中的其餘成員也能夠訪問。
  • 何時須要查詢取代
    • 用一個臨時變量保存其某一表達式的運算結果,須要一個查詢函數取代臨時變量
  • 怎麼取代
    • 須要分解臨時變量(臨時變量被賦值超過一次),以查詢取代臨時變量,而後再替換臨時變量
    • 首先應該將查詢設置爲私有的,當往後須要的時候再開放保護。
    • 不用考慮細微的性能問題,由於首先須要良好的架構才能使得程序正常運行。而後再考慮性能問題。

五、引入解釋性變量

  • Introduce Explaining Variable
    • 在引入解釋性變量以後,可使用導出方法或者用查詢取代臨時變量將臨時變量替換掉。
    • 1572396792964
  • 動機
    • 使得複雜表達式能夠閱讀和管理
  • 何時須要引入
    • 有一個複雜的表達式
  • 怎麼引入
    • 講一個複雜表達式(或一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式的用途
  • 與提煉函數的區別
    • 再提煉函數須要花費更大的工做量的時候

六、分解臨時變量

  • Split Temporary Variable
    • 1572396764394
  • 動機
    • 若是一個臨時變量承擔太多的職責,會使得閱讀者糊塗
  • 何時分解
    • 程序中有某個臨時變量被賦值超過一次,它既不是循環變量也不是收集計算結果。
  • 怎麼分解
    • 修改臨時變量的名稱並聲明爲常量

七、移除對參數的賦值*

  • Remove Assignments to Parameters
    • 這邊的是針對函數參數體成員
    • 對參數的賦值的想法是比較危險的,一旦爲參數進行賦值若是混淆值類型和引用類型很是容易產生不易察覺的錯誤。
    • 1572396741479
  • 動機
    • 由於面向對象的方式,因此數值類型的改變並不會改變原來傳入的值,可是引用類型就會變化
    • 致使混用按值傳遞和按引用傳遞
  • 何時移除
    • 代碼對函數的一個參數進行賦值時
  • 怎麼移除
    • 經過創建一個臨時變量,對臨時變量進行修改,而後返回臨時變量。
    • 若是須要返回一大堆函數,能夠將返回的一大堆函數變成一個單一的對象,或者爲每一個返回值設置一個獨立函數。
    • 還能夠在函數的每一個參數中增長一個const,這個方法只是在函數體較長的時候纔可使用。

八、以函數對象取代函數

  • Replace Method with Method Object
    • 1572396709204
  • 動機
    • 小型函數優美動人
  • 何時取代
    • 有一個大型函數,對其中的局部變量的使用沒法採用提煉方法的手段
  • 怎麼提取
    • 創建一個新類,將全部的局部變量變成字段,而後將原函數體中的邏輯變成方法。

九、替換算法

  • Substitute Algorithm
    • 1572396971680
  • 動機
    • 發現一個算法的效率更高的時候
  • 何時替換
    • 想法把某個算法換成另外一個更爲清晰的算法

2 在對象之間搬移特性

在面向對象的設計中,決定把責任放在哪裏。算法

先使用移動字段,在移動方法數據庫

一、搬移函數

  • Move Method
    • 1572397619571
  • 動機
    • 一個類與另外一個類高度耦合,就會搬移函數,經過這種手段,可使得類更加簡單。
  • 何時搬移
    • 有個函數與其所屬類以外的另外一個類有更多的交流。
    • 當不能確定是否須要移動一個函數,須要繼續觀察其餘函數,先移動其它函數就會使決定變得容易一些。
  • 怎麼搬移
    • 檢查全部字段,屬性和函數,考慮是否應該被搬移
    • 在該函數最經常使用引用中創建一個有相似行爲的新函數
    • 將舊函數變成一個單純的委託函數,或是將舊函數徹底移除。
    • 有多個函數使用這個須要搬移的特性,應考慮使用該特性的全部函數被一塊兒搬移。
    • 檢查全部子類和超類,看看是否有該函數其餘聲明
    • 若是目標函數使用了源類中的特性,能夠將源對象的引用看成參數(多個參數或則存在方法須要調用),傳給新創建的目標函數。
    • 若是目標函數須要太多源類特性,就得進一步重構,會將目標函數分解並將其中一部分移回源類。

二、搬移字段

  • Move Field
    • 1572397877822
  • 動機
    • 隨着項目類的增長和擴充,有一些字段放在原來的類中已經不太合適
  • 何時搬移
    • 某個字段在另外一個類中被更多的用到
  • 怎麼搬移
    • 修改源字段的全部用戶,令它們改用新字段
    • 決定如何在源對象中引用目標對象,方法,新建字段引用
    • 新類中自我封裝SetValue, GetValue。

三、提煉類*?

  • Extract Class
    • 1572397904071
  • 動機
    • 將複合類的職責提煉出新的類
    • 或者須要將類的子類化,分解原來的類
  • 何時提煉
    • 某個類作了應該由兩個類作的事
  • 怎麼提煉
    • 創建一個新類,將相關的字段和函數從舊類搬移到新類
    • 有可能須要一個雙向鏈接, 可是在真正須要它以前,不要創建重新類往舊類的鏈接,若是創建起雙向鏈接,檢查是否能夠將它改成單向鏈接。

四、將類內聯化

  • Inline Class
    • 1572400836706
  • 動機
    • 一個類再也不承擔足夠責任,再也不由單獨存在的理由。
  • 何時內聯
    • 某個類沒有作太多的事情
  • 怎麼內聯
    • 將這個類是多有特性搬移到另外一個類中,而後移除原類
    • 修改全部源類引用點,改而引用目標類

五、隱藏「委託關係」

  • Hide Delegate編程

    • 侷限性是每當客戶要使用受託類的新特性時,就必須在服務段添加一個簡單委託函數,受託類的特性愈來愈多,這一過程會愈來愈痛苦。設計模式

    • 1572400926241

    • 簡單委託關係1572400978816數組

  • 動機session

    • 封裝意味着每一個對象都應該儘量少的瞭解系統的其餘部分,
    • 若是客戶調用對象字段獲得另外一個對象,而後再調用後者的函數,那麼客戶就必須知道這一層關係。將委託關係隱藏起來不會波及客戶。
  • 何時隱藏多線程

    • 客戶經過一個委託類來調用另外一個對象
  • 怎麼隱藏架構

    • 在服務類上創建客戶所需的全部函數,用以隱藏委託關係
    • 1572253642885
    • manager=john.getDepartment().getManager();隱藏=>manager=john.getManager();隱藏了調用關係。

六、移除中間人

  • Remove Middle Man
    • 與隱藏委託關係相反
    • 1572401291820
  • 動機
    • 針對隱藏委託的侷限性,當委託的方法愈來愈多時,服務類就徹底變成一箇中間人,此時應該讓客戶直接調用受託類。
  • 何時移除
    • 某個類作了過多的簡單委託動做
  • 怎麼移除
    • 讓客戶直接調用受託類

七、引入外加函數

  • Introduce Foreign Method
    • 1572401389300
  • 動機
    • 發現一個好用的工具類不能修改工具類,添加方法
    • 但外加函數終歸是權益之計,
  • 何時須要引入外加函數
    • 須要爲提供服務的類增長一個函數,但沒法修改這個類。
  • 怎麼引入
    • 在客戶類中創建一個函數,並以第一參數形式傳入一個服務類實例

八、引入本地擴展

  • Introduce Local Extension
    • 1572401403718
  • 動機
    • 在不能修改的類中添加方法,方法的數量超過2個的時候外加函數難以控制,須要將函數組織到一塊兒,經過兩種標準對象技術:子類化和包裝,子類化和包裝叫作本地擴展。
    • 在子類化和包裝中優先選擇子類,
    • 使用包裝會形成A=B,B不等於A的邏輯,子類等於包裝類,包裝類不等於子類
  • 何時引入
    • 須要爲服務類提供一些額外函數,但沒法修改類。
  • 怎麼引入
    • 創建一個新類,使它包含這些額外函數,讓這個擴展品成爲源類的子類或包裝類。
    • 子類化方案,轉型構造函數應該調用適當的超類構造函數
    • 包裝類方案,轉型構造函數應該傳入參數以實例變量的形式保存起來,用做接受委託的原對象。

3 從新組織數據

對於這個類的任何修改都應該經過該類的方法。類擁有一些數據卻無所覺,擁有一些依賴無所覺是很是危險的。因此纔要封裝字段,封裝集合,監視數據,用對象替代數組,用對象替代集合,關聯改動。app

一、自封裝字段

  • Self Encapsulate
    • 1572401773280
  • 動機
    • 直接訪問變量的好處:子類能夠經過覆寫一個函數而改變獲取數據的途徑,它還支持更靈活的數據管理方式,如延遲初始化等,
    • 直接訪問變量的好處:代碼比較容易閱讀,
    • 優先選擇直接訪問的方式,直到這種訪問方式帶來麻煩位置。
  • 何時須要自封裝字段
    • 直接訪問一個字段,但與字段之間的耦合關係逐漸變得笨拙。
  • 怎麼自封裝
    • 爲這個字段創建取值/設值函數,而且只以這些函數來訪問字段。

二、以對象取代數據值

  • Replace Data Value with Object
    • 1572402017495
  • 動機
    • 簡單數據再也不簡單,
    • 注意:原來的數據值是值對象,改爲對象可能變成引用類型,這樣面臨的問題是多個實例就不是同一個對象。須要用將引用對象改爲值對象方法,
  • 何時須要對象取代
    • 有一個數據項,須要與其餘數據和行爲一塊兒使用纔有意義。
  • 怎麼對象取代
    • 爲替換值新建一個新類,其中聲明final字段,修改原字段的引用,都修改成對象。

三、將值對象改爲引用對象

  • Change Value to Reference
    • 對於值類型來講,equals和==的功能是相等的都是比較變量的值、
    • 對於引用類型來講,==是b比較兩個引用是否相等,equals是比較的引用類型的內容是否相等,而使用equals是須要重寫的,否則就是調用object中的equals
    • 1572402839811
  • 動機
    • 值對象通常是基本數據類型,並不在乎是否有副本的存在,
    • 引用對象是否相等,直接使用==操做符
  • 何時改引用
    • 一個類衍生出許多彼此相等的實例,但願將它們替換爲同一個對象
    • 類的每一個實例中的字段都是獨立,就是值類型,每一個實例都對應一個字段對象。
    • 引用類型多個實例能夠共用一個字段對象。不是全部
  • 怎麼改
    • 建立簡單工廠和註冊表,工廠負責生產字段對象,註冊表負責保存全部的字段對象
    • 類實例經過工廠請求字段實例,工廠經過訪問註冊表返回字段實例引用。
  • 例子
    • 1572403867513
    • 1572403915291
    • 1572403943801
    • 目前爲止customer對象仍是值對象,即便多個訂單屬於同一客戶但每一個order對象仍是擁有本身的customer對象。
    • 使用工廠方法替代構造函數
    • 1572404251004
    • 1572404268577
    • 1572404275823
    • 此時值對象才變成引用對象,多個實例間都共享同一個引用對象

四、將引用對象改爲值對象

  • Change Reference to value
    • 這邊引用對象改爲值對象並非說須要把引用類型改爲基本類型,而是即便引用類型是不一樣副本,那麼相同內容的引用內容也是相等(重寫Equals())
    • 1572404484481
  • 動機
    • 若是引用對象開始變得難以使用,或許就應該將它改爲值對象。
    • 引用對象必須被某種方式控制,並且必須向其控制者請求適當的引用對象,會形成區域之間錯綜複雜的關聯。
    • 值對象應該是不可變的(不管什麼時候,調用同一個對象的同一個查詢函數都應該獲得相同的結果),若是須要改變就須要從新建立一個所屬類的實例,而不是在現有對象上修改。
  • 何時更改
    • 有一個引用對象,很小且不可變,並且不易管理。
  • 怎麼更改
    • 檢查重構目標是否爲不可變對象,創建equals和hashcode方法
    • new Currency("USD").equals(new Currency("USD"));返回false。重寫equal和hashcode使其返回true,這樣對象就是值對象,不可變。

五、以對象取代數組

  • Replace Array with Object
    • 1572405339305
  • 動機
    • 數組是常見的組織數據的結構,只用於以某種順序容納一組類似對象。
  • 何時須要取代
    • 有一個數組,其中的元素各自表明不一樣的東西
  • 怎麼取代
    • 將數組的每一個不一樣意思都抽象稱字段

六、複製被監視的數據

  • Duplicate Observed Data
    • 1572405469613
  • 動機
    • 一個分層良好的系統,用戶界面和處理業務邏輯的代碼分開
    • MVC模式
  • 何時須要複製
    • 有一些領域數據置身於GUI控件中,而鄰域函數須要訪問這些數據
  • 怎麼複製
    • 將該數據複製到一個領域對象中,創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據

七、將單向關聯改爲雙向關聯

  • Change Unidirectional Association to Bidirectional

    • 有點像觀察者模式,控制者是訂閱端,被控制者是主題,主題存在輔助函數,用於修改反向指針,訂閱端調用輔助函數來修改反向指針。
    • 1572405656193
  • 動機

    • 隨着項目時間的推移須要雙向關聯
  • 何時改動

    • 兩個類都須要使用對方特性,但其間中有一條單向鏈接
  • 怎麼實現

    • 添加一個反向指針,並使修改函數可以同時更新兩條鏈接。

    • 在被引用的類中增長一個字段,保存反向指針。

    • 控制端和被控制端

      • 一對多的關係,可使用單一引用的一方(就是多的那一方)承擔控制者的角色。
      • 對象是組成另外一對象的部件,該部件負責控制關聯關係
      • 若是二者都是引用對象,多對多,那麼無所謂。
    • 被控端創建一個輔助函數負責修改反向指針

    • 若是既有的修改函數在控制端,讓它負責控制修改反向指針

    • 若是既有的修改函數在被控端,就在控制端創建一個控制函數,並讓既有的修改函數調用這個新建的控制函數,來控制修改反向指針。

八、將雙向關聯改成單向關聯

  • Change Bidirectional Association to Unidirectional
    • 1572406648238
  • 動機
    • 雙向關聯必需要符出代價,維護雙向關聯,確保對象被正確建立和刪除而增長的複雜度。
    • 雙向關聯還會形成殭屍對象,某個對象已經死亡卻保留在系統中,由於它的引用尚未徹底清楚。
    • 雙向關聯也會迫使兩個類之間有了依賴,對其中任一個類的修改,均可能引起另外一個類的變化。
  • 何時須要
    • 兩個類之間有雙向關聯,但其中一個類再也不須要另外一個的特性
  • 怎麼修改
    • 去除沒必要要的關聯
    • 將私有字段去掉,須要依賴的函數,將依賴類做爲參數傳入,而後調用。
    • 建立一個靜態字典保存全部的依賴類,經過取值函數來得到字段遍歷對比依賴的引用是否相同來獲取依賴類。

九、以字面常量取代魔法數

  • Replace Magic Number with Symbolic Constant
    • 1572406815589
  • 動機
  • 何時取代
    • 有一個字面數值,並帶有特別含義
  • 怎麼取代
    • 創造一個常量,根據其意義爲它命名,並將上述的字面數值替換爲這個常量。

十、封裝字段

  • Encapsulate Field
    • 1572406832493
  • 動機
    • 數據聲明爲public被看作一種很差的作法,會下降模塊化程度。
    • 擁有該數據對象卻毫無察覺,不是一件好事
  • 何時封裝
    • 類中存在一個public字段
  • 怎麼封裝
    • 將原字段聲明爲private,並提供相應的訪問函數

十一、封裝集合

  • Encapsulate Collection
    • 除非經過封裝的集合類,否則沒有任何實例可以修改這個集合。
    • 1572406851542
  • 動機
    • 在一個類中使用集合並將集合給取值函數,但類不該該返回集合自身,由於這回讓用戶得以修改集合內容而對集合的使用者一無所知。
    • 不該該爲集合提供一個設值函數,但應該爲集合添加/移除元素的函數,這樣集合的擁有者就能夠控制集合元素的添加和移除。
  • 何時封裝
    • 有一個函數返回一個集合
  • 怎麼封裝
    • 讓這個函數返回該集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數

十二、以數據類取代記錄

  • Replace Record with Data Class
  • 動機
    • 從數據庫讀取的記錄,須要一個接口類,用來處理這些外來數據。
  • 何時作
    • 須要面對傳統編程環境中的記錄結構
  • 怎麼作
    • 爲該記錄建立一個啞數據對象。
    • 新建一個類,對於記錄彙總的每一項數據,在新建的類中創建一個對應的private字段,並提供相應的取值和設值函數。

1三、以類取代類型碼

  • Replace Type Code with Class
    • 原來的類型碼多是int類型,創建一個類型碼的類,全部的int轉換成類型碼的類,其實有點像建立一個枚舉類型,而後用枚舉類型取代int。
    • 1572411176016
  • 動機
    • 類型碼或枚舉值很常見,但終究只是一個數值,若是是一個類就會進行類型檢驗,還能夠爲這個類提供工廠函數,保證只有合法的實例纔會被建立出來。
    • 若是有switch必須使用類型碼,但任何switch都應該使用多態取代條件去掉。爲了進行這樣的重構還須要使用子類取代類型碼,用狀態或策略替換類型碼。
  • 何時作
    • 類之中有一個數值類型碼,但它並不影響類的行爲
  • 怎麼作
    • 以一個新的類替換該數值類型碼
    • 用以記錄類型碼的字段,其類型應該和類型碼相同,還應該有對應的取值函數,還應該用一組靜態變量保存容許被建立的實例,並以一個靜態函數根據本來的類型碼返回合適的實例。

1四、以子類取代類型碼

  • Replace Type Code with Subclasses
    • 1572486570053
  • 動機
  • 何時作
    • 有一個不可變的類型碼,它會影響類的行爲
    • 若是類型碼會影響宿主類的行爲,最好的作好就是用多態來處理變化行爲。就是switch和if else結構。
    • 類型碼值在對象船艦以後發生變化,類型碼宿主類已經擁有子類,這兩種狀況下就須要使用狀態/策略設計模式
  • 怎麼作
    • 以子類取代這個類型碼

1五、以State/Strategy取代類型碼

  • Replace Type Code with State/Strategy
    • 每一個狀態有特定的數據和動做。
    • 1572486550695
  • 動機
  • 何時作
    • 有一個類型碼,它會影響類的行爲,但沒法經過繼承手法消除它
  • 怎麼作

1六、以字段取代子類

  • Replace Subclass with Fields
    • 1572486531862
  • 動機
  • 何時作
    • 各個子類的惟一差異只在返回常量數據的函數身上
    • 直接用該字段的不一樣值表示子類就能夠了。
  • 怎麼作
    • 修改這些函數,使它們返回超類中某個(新增字段,而後銷燬子類)

4 簡化條件表達式

一、分解條件表達式

  • Decompose Conditional
    • 1572486513216
  • 動機
    • 複雜的條件邏輯是最常致使複雜度上升的地點之一,
  • 何時作
    • 有一個複雜的條件語句
  • 怎麼作
    • 從if,then,else三個段落中分別提煉出獨立函數
    • 將其分解爲多個獨立函數,根據每一個小塊代碼的用途分解而得的新函數命名。
    • 不少人都不肯意去提煉分支條件,由於這些條件很是短,可是提煉以後函數的可讀性很強,就像一段註釋同樣清楚明白。

二、合併條件表達式

  • Consolidate Conditional Expression
    • 其實就是用一個小型函數封裝一下,小型函數的名字能夠做爲註釋。
    • 1572486838506
  • 動機
    • 合併後的條件代碼會使得檢查的用意更加清晰,合併前和合並後的代碼有着相同的效果。
  • 何時作
    • 有一系列條件測試,都獲得相同結果
  • 怎麼作
    • 將這些測試合併爲一個條件表達式,並將這個條件表達式提煉成爲一個獨立函數。

三、合併重複的條件片斷

  • Consolidate Duplicate Conditional Fragments
    • 1572487145359
  • 動機
  • 何時作
    • 在條件表達式的每一個分支上都有着相同的一段代碼
  • 怎麼作
    • 將這段重複代碼搬移到條件表達式以外。

四、移除控制標記

  • Remove Control Flag
  • 動機
    • 單一出口原則會迫使讓媽中加入討厭的控制標記,大大下降條件表達式的可讀性,
  • 何時作
    • 在一系列布爾表達式中,某個變量帶有"控制標記"(control flag)的做用
  • 怎麼作
    • break語句或return語句取代控制標記

五、以衛語句取代嵌套條件表達式

  • Replace Nested Conditional with Guard Clauses
    • 1572487743463
  • 動機
    • 單一出口的規則其實並非那麼有用,保持代碼清晰纔是最關鍵的。
  • 何時作
    • 函數中條件邏輯令人難以看清正常的執行路徑
  • 怎麼作
    • 使用衛語句表現全部特殊狀況。

六、以多態取代條件表達式

  • Replace Conditional with Polymorphism
    • 1572488390812
  • 動機
    • 若是須要根據對象的不一樣類型而採起不一樣的行爲,多態使你沒必要編寫明顯的條件表達式。
    • 同一組條件表達在程序許多地點出現,那麼使用多態的收益是最大的。
  • 何時作
    • 有一個條件表達式,根據對象類型的不一樣而選擇不一樣的行爲
  • 怎麼作
    • 將這個體哦阿健表示式的每一個分支放進一個子類的覆寫函數中,而後將原始函數聲明爲抽象函數。

七、引入Null對象

  • Introduce Null Object
    • 1572488884565
  • 動機
    • 多態的最根本好處就是沒必要要想對象詢問你是什麼類型然後根據獲得的答案調用對象的某個行爲,只管調用該行爲就是了。
    • 空對象必定是常量,它們的任何成分都不會發生變化,所以可使用單例模式來實現它們。
  • 何時作
    • 須要再三檢查對象是否爲Null
  • 怎麼作
    • 將null對象替換成null對象。

八、引入斷言

  • Introduce Assertion
    • 1572489734260
  • 動機
    • 斷言是一個條件表達式,應該老是爲真,若是它失敗,表示程序員犯了一個錯誤。所以斷言的失敗應該致使一個非受控異常(unchecked exception)。
    • 加入斷言永遠不會影響程序的行爲。
    • 用它來檢查必定必須爲真的條件。
  • 何時作
    • 某一段代碼須要對程序狀態作出某種假設
  • 怎麼作
    • 以斷言明確表現這種假設

5 簡化函數調用

全部的數據都應該隱藏起來。

一、函數更名

  • Rename Method
    • 1572491178470
  • 動機
    • 將複雜的處理過程分解成小函數。
  • 何時作
    • 函數名稱未能揭示函數的用途
  • 怎麼作
    • 修改函數名稱

二、添加參數

  • Add Parameter
    • 1572491269669
  • 動機
  • 何時作
    • 某個函數須要從調用端獲得更多信息
    • 在添加參數外經常還有其餘的選擇,只要有可能,其餘選擇都比添加參數要好(查詢),由於它們不會增長參數列的長度,過長的參數列是一個很差的味道。
  • 怎麼作
    • 爲此函數添加一個對象參數,讓該對象帶進函數所需信息。

三、移除參數

  • Remove Parameter
    • 1572491400021
  • 動機
    • 可能常常添加參數卻不多去除參數,由於多餘的參數不會引發任何問題,相反之後可能還會用到它。請去除這些想法。
  • 何時作
    • 函數本體不須要某個參數
  • 怎麼作
    • 將該參數去除。

四、將查詢函數和修改函數分離

  • Separate Query from Modifier
    • 1572491591701
  • 動機
    • 在多線程系統中,查詢和修改函數應該被聲明爲synchronized(已同步化)
  • 何時作
    • 某個函數既返回對象狀態值,又修改對象狀態
    • 任何有返回值的函數,都不該該又看獲得的反作用。
    • 常見的優化是將某個查詢結果放到某個字段或集合中,後面如何查詢,老是得到相同的結果。
  • 怎麼作
    • 創建兩個不一樣的函數,其中一個負責查詢,另外一個負責修改。

五、令函數攜帶參數

  • Parameterize
    • 1572567550304
  • 動機
    • 去除重複代碼
  • 何時作
    • 若干函數作了相似的工做,但在函數本體中卻飽含了不一樣的值
  • 怎麼作
    • 創建單一函數,以參數表達那些不一樣的值

六、以明確函數取代參數

  • Replace Parameter with Explicit Methods
    • 1572567656198
  • 動機
    • 避免出現條件表達式,接口更清楚,編譯期間就能夠檢查,
    • 若是在同一個函數中,參數是否合法還須要考慮
    • 可是參數值不會對函數的行爲有太多影響的話就不該該使用本項重構,若是須要條件判斷的行爲,能夠考慮使用多態。
  • 何時作
    • 有一個函數,其中徹底取決於參數值不一樣而採起不一樣行爲
  • 怎麼作
    • 針對該參數的每個可能值,創建一個獨立函數

七、保持對象完整

  • Preserve While Object
    • 1572568707669
  • 動機
    • 不適用完整對象會形成重複代碼
    • 事物都是有兩面性,若是你傳的是數值,被調用函數就只依賴於這些數值,若是傳的是對象,就要依賴於整個對象。若是依賴對象會形成結構惡化。那麼就不該該使用保持對象完整。
    • 若是這個函數使用了另外一個對象的多項數據,這可能覺得着這個函數實際上應該定義在那些數據所屬的對象上,應該考慮移動方法。
  • 何時作
    • 從某個對象中取出若干值,將它們做爲某一次函數調用時的參數
  • 怎麼作
    • 改成傳遞整個對象

八、以函數取代參數

  • Replace Parameter with Methods
    • 1572569273989
  • 動機
    • 儘量縮減參數長度
  • 何時作
    • 對象調用某個函數,並將全部結果做爲參數傳遞給另外一個函數,而接受該參數的函數本省也可以調用前一個函數。
  • 怎麼作
    • 讓參數接受者去除該項參數,並直接調用前一個函數。

九、引入參數對象

  • Introduce Parameter Object
    • 1572569919231
  • 動機
    • 特定的一組參數老是一塊兒被傳遞,可能有好幾個函數都使用這一組參數,這些函數可能隸屬於同一個類,也可能隸屬於不一樣的類。這樣的參數就是所謂的數據泥團,能夠運用一個對象包裝全部的這些數據,再以該對象取代它們。
  • 何時作
    • 某些參數老是很天然地同時出現
  • 怎麼作
    • 以一個對象取代這些參數

十、移除設值函數

  • Remove Setting Method
  • 動機
    • 使用了設值函數就暗示了這個字段值能夠被改變。
  • 何時作
    • 類中某個字段應該在對象建立時被設值,而後就再也不改變。
  • 怎麼作
    • 去掉該字段的全部設值函數。

十一、隱藏函數

  • Hide Method
    • 1572570692493
  • 動機
    • 面對一個過於豐富、提供了過多行爲的接口時,就值得將非必要的取值函數和設置函數隱藏起來
  • 何時作
    • 有一個函數,歷來沒有被其餘任何類用到
  • 怎麼作
    • 將這個函數修改成private

十二、以工廠函數取代構造函數

  • Replace Constructor with Factory Method
    • 1572570846559
  • 動機
    • 使用以工廠函數取代構造函數最顯而易見的動機就是在派生子類的過程當中以工廠函數取代類型碼
    • 工廠函數也是將值替換成引用的方法。
  • 何時作
    • 但願在建立對象時不只僅是作簡單的構建動做
  • 怎麼作
    • 將構造函數替換爲工廠函數
    • 使用工廠模式就使得超類必須知曉子類,若是想避免這個能夠用操盤手模式,爲工廠類提供一個會話層,提供對工廠類的集合對工廠類進行控制。

1三、封裝向下轉型

  • Encapsulate Downcast
    • 1572572975294
  • 動機
    • 能不向下轉型就不要向下轉型,但若是須要向下轉型就必須在該函數中向下轉型。
  • 何時作
    • 某個函數返回對象,須要由函數調用者執行 向下轉型
  • 怎麼作
    • 將向下轉型動做移到函數中

1四、以異常取代錯誤碼

  • Replace Error Code with Exception
    • 1572573309472
  • 動機
    • 代碼能夠理解應該是咱們虔誠最求的目標。
  • 何時作
    • 某個函數返回一個特定的代碼,用以表示某種錯誤狀況
  • 怎麼作
    • 改用異常
    • 決定應該拋出受控(checked)異常仍是非受控(unchecked)異常
      • 若是調用者有責任在調用前檢查必要狀態,就拋出非受控異常
      • 若是想拋出受控異常,能夠新建一個異常類,也可使用現有的異常類。
    • 找到該函數的全部調用者,對它們進行相應調整。
      • 若是函數拋出非受控異常,那麼就調整調用者,使其在調用函數前作適當檢查,
      • 若是函數拋出受控異常,那麼就調整調用者,使其在try區段中調用該函數。

1五、以測試取代異常

  • Replace Exception with Test
    • 1572576674845
  • 動機
    • 在異常被濫用的時候
  • 何時作
    • 面對一個調用者能夠預先檢查的體哦阿健,你拋出一個異常
  • 怎麼作
    • 修改調用者,使它在調用函數以前先作檢查

6 處理繼承關係

一、字段上移

  • Pull Up Field
    • 1572577009547
  • 動機
    • 減小重複
  • 何時作
    • 兩個子類擁有相同的字段
  • 怎麼作
    • 將該字段移至超類

二、函數上移

  • Pull Up Method
    • 1572577175581
  • 動機
    • 滋生錯誤
    • 避免重複
  • 何時作
    • 有些函數在各個子類中產生徹底相同的結果
  • 怎麼作
    • 將該函數移至超類
    • 最煩的一點就是,被提高的函數可能會引用子類中出現的特性,若是被引用的是一個函數能夠將這個函數一同提高至超類,或則在超類中創建一個抽象函數。
    • 若是兩個函數類似但不相同,能夠先借助塑造模板函數。

三、構造函數本體上移

  • Pull Up Constructor Body
    • 1572577601688
  • 引用
    • 若是重構過程過於複雜,能夠考慮使用工廠方法。
  • 何時作
    • 在各個子類中擁有一些構造函數,它們的本體機會徹底一致
  • 怎麼作
    • 在超類中新建一個構造函數,並在子類構造函數中調用它。

四、函數下移

  • Push Down Method
    • 1572577790347
  • 動機
    • 把某些行爲從超類移動到特定的子類中。
  • 何時作
    • 超類中某個函數只與部分子類有關
  • 怎麼作
    • 將這個函數移到相關的那些子類中
    • 若是移動的函數須要使用超類中的某個字段,則須要將超類中的字段的開放protected.

五、字段下移

  • Push Down Field
    • 1572577916400
  • 動機
  • 何時作
    • 超類中的某個字段只被部分子類用到
  • 怎麼作
    • 將這個字段移到須要它的那些子類去

六、提煉子類*?

  • Extract Subclass
    • 1572578034339
  • 動機
    • 類中的某些行爲只被一部分實例用到,其餘實例不須要,有時候這些行爲上的差別是經過類型碼分區的,可使用子類替換類型碼,或則使用狀態或策略模式替代類型碼。
    • 抽象類和抽象子類則是委託和繼承之間的抉擇
    • 抽象子類會更加容易,可是一旦對象創建完成,沒法再改變與類型相關的行爲。
  • 何時作
    • 類中的某些特性只被某些實例用到
  • 怎麼作
    • 新建一個子類,將上面所說的那一部分特性移到子類中
    • 爲源類定一個新的子類
    • 爲這個新的子類提供構造函數
      • 讓子類構造函數接受與超類構造函數相同的參數,並經過super調用超類的構造函數。
      • 用工廠替換構造函數
    • 找出調用結果超類構造函數的全部地點,新建子類
    • 下移方法和字段

七、提煉超類*?

  • Extract Superclass

    • 1572578336169
  • 動機

  • 何時作

    • 兩個類有類似特性
  • 怎麼作

    • 爲這兩個類創建一個超類,將相同特性移至超類。
  • 新建一個空白抽象類

    • 上移字段和方法
      • 先搬移字段
      • 子類函數中有相同的簽名,但函數體不一樣,能夠抽象函數
      • 若是方法中有相同算法,可使用提煉算法,將其封裝到同一個函數中。

    八、提煉接口

    • Extract Interface
      • 1572579063782
    • 動機
      • 類之間彼此互用的方式有若干種,某一種客戶只使用類責任區的一個特定子集。
      • 某個類在不一樣環境下扮演大相徑庭的角色,使用接口就是一個好主意。
    • 何時作
      • 若干客戶使用類接口中同一個子集,或者兩個類的接口有部分相同
    • 怎麼作
      • 將相同的子類提煉到一個獨立接口中。

九、摺疊繼承關係

  • Collapse Hierarchy
    • 1572579987774
  • 動機
  • 何時作
    • 超類和子類之間無太大區別
  • 怎麼作
    • 將它們合爲一體

十、塑造模板函數

  • Form Template Method
    • 1572580131316
  • 動機
    • 既避免重複也保持差別。
  • 何時作
    • 有一些子類,其中相應的某些函數以相同順序執行相似的操做,但各個操做的細節上有所不一樣。
  • 怎麼作
    • 將這些操做分別放進獨立函數中,並保持它們都有相同的簽名,因而原函數也就變得相同的,而後將原函數上移至超類

十一、以委託取代繼承

  • Replace Inheritance with Delegation
    • 1572580380411
  • 動機
    • 超類中有許多操做並不真正適用於子類,這種狀況下,你所擁有的接口並未真正反映出子類的功能。
  • 何時作
    • 某個子類只使用超類接口中的一部分,或是根本不須要繼承而來的數據
  • 怎麼作
    • 在子類中新建一個字段用以保存超類,調整子類函數,令它改而委託超類,而後去掉二者之間的繼承關係。
    • 在子類中新建一個字段,使其引用超類的實例
    • 修改子類中的全部函數,讓它們再也不使用超類,轉而使用上述那個受託字段。

十二、以繼承取代委託

  • Replace Delegation with Inheritance
    • 1572583811463
  • 動機
    • 若是並無使用受託類的全部函數,就不該該使用用繼承替換委託,
    • 可使用去除中間層的方法讓客戶端本身調用受託函數。
  • 何時作
    • 在兩個類之間使用委託關係,並常常爲整個接口編寫許多極簡單的委託函數。
  • 怎麼作

7 大型重構

一、梳理並分解繼承體系

  • Tease Apart Inheritance
    • 就是讓每一個類的職責更明確更單一,當一個類的職責混亂時,經過繪製職責圖來分離職責,並建立另外一個超類,將相關的字段和方法都移動到另外一個超類
    • 1572653896760
  • 動機
    • 混亂的繼承體系是一個嚴重的問題,會致使重複代碼,然後者正是程序員生涯的致命毒藥。還會使修改變得困難,由於特定問題的解決決策被墳山到了整個繼承體系。
  • 何時作
    • 某個繼承體系同時承擔兩項責任
  • 怎麼作
    • 創建兩個繼承體系,並經過委託關係讓其中一個能夠調用另外一個
    • 首先識別出繼承體系所承擔的不一樣責任,而後創建一個二維表格(或則三位乃至四維表格),並以座標軸標示不一樣的任務,
    • 判斷哪一項責任更重一些,並準備將它留在當前的繼承體系中,準備將另外一項責任移到另外一個繼承體系中。
    • 使用抽象類方法從當前的超類提煉出一個新類,用以表示重要性稍低的責任,並在原超類中添加一個實例變量,用以保存新類的實例。
    • 對應於原繼承體系中的每一個子類,建立上述新類的一個子類,在原繼承體系的子類中,將前一步驟所添加的實例變量初始化爲新建子類的實例。
    • 針對原繼承體系中的每一個子類,使用搬移函數的方法遷移到與之對應的子類中。
    • 1572658453762
    • 1572658467189
    • 1572658478819
    • 1572658495471
    • 1572658514062

二、將過程化設計轉化爲對象設計

  • Convert Procedural Design to Objects
    • 1572659942645
  • 動機
  • 何時作
    • 有一些傳統過程化風格的代碼
  • 怎麼作
    • 將數據記錄變成對象,將大塊的行爲分爲小塊,並將行爲移入相關對象之中。
    • 針對每個記錄類型,將其轉變爲只含訪問函數的啞數據對象
    • 針對每一處過程化風格,將該出的代碼提煉到一個獨立類中。
    • 針對每一段長長的程序,試試提煉方法將長方法分解並將分解後的方法移動到相關的啞數據類。

三、將領域和表訴/顯示分離

  • Separate Domain from Presentation

    • 1572660551940
  • 動機

    • MVC模式最核心的價值在於,它將用戶界面代碼(即視圖:亦即現今常說的展示層)和領域邏輯(即模型)分離了,展示類只含用以處理用戶界面的邏輯:領域類包含任何與程序外觀相關的代碼,只含業務邏輯相關代碼,將程序中這兩塊複雜的部分加以分離,程序將來的修改將變得更加容易,同時也使用贊成業務邏輯的多種展示方式稱爲可能。
  • 何時作

    • 某些GUI類中包含了領域邏輯
  • 怎麼作

    • 將領域邏輯分離出來,爲它們創建獨立的鄰域類。

    • 爲每一個窗口創建一個領域類,

    • 若是窗口內含有一張表格,新建一個類來表示其中的行,再以窗口所對應之領域類中的一個集合來容納全部行領域對象

    • 檢查窗口中的數據,若是數據只被用於UI,就把它留着,若是數據被領域邏輯使用,並且不顯示與窗口上,咱們就可使用移動方法將它搬移到領域類中,若是數據同時被UI和領域邏輯使用,就對它實施複製被監視數據,使它同時存在於兩處,並保持兩處之間的同步。

    • 展示類中的邏輯,實施提煉方法將展示邏輯從鄰域邏輯中分開,一旦隔離了鄰域邏輯,在運用搬移方法將它移到鄰域類。

    • 1572661870682

    • 1572661971112

四、提煉繼承體系

  • Extract Hierarchy
    • 1572662102757
  • 動機
    • 一開始設計者只想以一個類實現一個概念,但隨着設計方案的演化,最後可能一個類實現兩個三乃至十個不一樣的概念。
  • 何時作
    • 有某個類作了太多的工做,其中一部分工做是以大量條件表達式完成的
  • 怎麼作
    • 創建繼承體系,以一個子類表示一種特殊狀況。
    • 有兩種重構的手法
      • 沒法肯定哪些地方會發生變化
    • 不肯定哪些地方會發生變化
      • 鑑別出一中變化狀況,
        • 若是這種拜年話可能在對象聲明週期的不一樣階段而有不一樣體現就用提煉方法將它提煉爲一個獨立的類
      • 針對這種變化狀況,新建一個子類,並對原始類實施工廠方法替代構造函數,再次修改工廠方法,令它返回適當的子類實例。
      • 將含有條件邏輯的函數,一個個複製到子類
        • 有必要隔離函數中的條件邏輯和非條件邏輯。
      • 刪除超類中那些被全部子類覆寫的函數本體,並將它們聲明爲抽象函數。
    • 肯定原始類中每一種變化
      • 針對原始類中每一種變化狀況,創建一個子類,
      • 使用工廠方法替代構造函數將原始類的構造函數轉變成工廠函數,並令它針對每一種變化狀況返回適當的子類實例。
        • 若是原始類中的各類變化狀況是以類型碼標示,使用子類替換類型碼,若是那些變化狀況在對象週期的不一樣階段會有不一樣體現,使用狀態和策略模式替換類型碼
      • 針對帶有條件邏輯的函數,實施用多態替換條件若是非整個函數的行爲有所變化,請先運行提煉方法將變化部分和不變部分分隔開來
相關文章
相關標籤/搜索