學習重構(4)-從新組織數據

1. Self Encapsulate Field(自封裝值域)算法

你直接訪問一個值域,但與值域之間的耦合關係逐漸變得笨拙。爲這個值域創建取值/設值函數(get/set),而且只以這些函數來訪問值域。數據庫

應用場景:若是你想訪問superclass中的一個值域,卻又想在subclass中將[對這個變量的訪問]改成一個計算後的值,這就是最該使用Self Encapsulate Field(171)的時候。[值域自我封裝]只是第一步。完成自我封裝以後,你能夠在subclass中根據本身的須要隨意覆寫取值/設值函數(getting/setting methods)。編程

示例:數組

private int _low, _high數據結構

boolean includes (int arg) {框架

    return arg >= _low && arg <= _high;dom

}編程語言

重構爲:模塊化

private int _low, _high函數

boolean includes (int arg) {

    return arg >= getLow() && arg <= getHigh();

}

int getLow() {return _low};

int getHigh() {return _high};

 

2. Replace Data Value with Object(以對象取代數據值)

將一些數據按照必定的規則或特徵整合到一個對象中

應用場景:開發代碼初期,僅須要一些簡單的數據,好比一我的的基本信息:姓名,身份證號碼,隨着時間的推移,還須要其餘的數據:手機號,地址,學歷等等,這樣就會羅列一排的變量,每次處理一我的的數據是,要傳入不少參數。此時將這我的的信息就能夠規整到一個類裏面,僅聲明一個對象便可,並且隨着這我的信息的持續豐富,不會每次都衝擊到原有的接口。

 

3. Change Value to Reference(將實值對象改成引用對象)

你有一個class,衍生出許多相等的實體,你但願將他們替換爲單對象。將這個實值對象變成一個引用對象。

應用場景:咱們通常使用一個類的時候都是須要了就new一個對象出來,這些每次new出來的對象就是實值對象,但不少場景下咱們會改變這些對象的內部狀態,而且須要對這些對象進行傳遞,此時用實值對象來知足這個訴求就會顯得很是笨拙,好比new出來一個car,而後設置下品牌是寶馬,設置下顏色是藍色等等。這種場景下咱們就須要一個引用對象,同一個類中能夠是成員變量,不一樣的類中使用可使用相似工廠、單例等模式來有一個對象管理類來供其餘類取用。

 

4. Change Reference to Value(將引用對象改成實值對象)

和3相反。

應用場景:實值對象一個重要特色是保持不變,這樣能夠保證每一個使用者不用擔憂數據的變化和同步。引用對象每每會讓邏輯變得複雜,因此使用的時候要作好選擇。

 

5. 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");

 

6. Duplicate Observed Data(賦值「被監視數據」)

你有一筆domain data置身於GUI空間中,而domain method須要訪問它們。將這筆數據拷貝到一個domain object中,創建一個Observer模式,用意對domain object和GUI object內的重複數據進行同步控制。

應用場景:一個分層良好的系統,應該將處理用戶界面和處理業務邏輯的代碼分開。之因此這樣作,緣由有一下幾點:(1)你可能須要使用不一樣的用戶界面來表現相同的業務邏輯,若是同時承擔兩種責任,用戶界面會變得過度複雜;(2)與GUI隔離後,領域對象的維護和演化都會更容易,甚至可讓不一樣的開發者負責不一樣部分的開發。儘管能夠輕鬆地將「行爲」劃分到不一樣部位,「數據」卻每每不能如此。同一項數據有可能既須要內嵌於GUI控件,也須要保存於領域模型裏。自從MVC模式出現後,用戶界面框架都是用多層系統來提供某種機制,使你不但能夠提供這類數據,並保持他們同步。若是代碼是以兩層方式開發,業務邏輯被內嵌於用戶界面之中,就有必要將行爲分離出來。其中的主要工做就是函數的分解和搬移。但數據就不一樣了:不能僅僅只是移動數據,必須將它複製到新對象中,並提供相應的同步機制。

 

7. Change Unidirectional Association to Bidirectional(將單項關聯改成雙向)

兩個classes都須要使用對方特性,但其間只有一條單向鏈接。添加一個反向指針,並使修改函數可以同時更新兩條鏈接。

應用場景:開發初期,你可能會在兩個classes之間創建一條單向鏈接,使其中一個class能夠引用另外一個class。隨着時間的推移,你可能發現被引用的class須要獲得其引用者來進行某些處理,也就是說它須要一個反向指針。

 

8. Change Bidirectional Association to Unidirectional(將雙向關聯改成單向)

應用場景:雙向關聯頗有用,但你也必須爲它付出代價,那就是「維護雙向鏈接,確保對象被正確的建立和刪除」而增長的複雜度。因此只有在你須要雙向關聯的時候,才應該使用它。若是你發現雙向關聯再也不有存在價值,就應該去掉其中沒必要要的一條關聯。

 

9. Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數字)

應用場景:在計算科學中,魔法數(magic number)是歷史最悠久的不良現象之一。所謂魔法數是指擁有特殊意義,卻又不能明確表現出這種意義的數字。若是你須要在不一樣的地點引用同一個邏輯數,魔法數會讓你煩惱不已,由於一旦這些數發生改變,你就必須在程序中找到全部魔法數,並將它們所有修改一遍,這簡直就是一場噩夢。就 算你不須要修改,要準確指出每一個魔法數的用途,也會讓你頗費腦筋。許多語言都容許你聲明常量。常量不會形成任何性能開銷,卻能夠大大提升代碼的可讀性。進行本項重構以前,你應該先尋找其餘替換方案。你應該觀察魔法數如何被使用,然後每每你會發現一種更好的使用方式。若是這個魔法數是個type code(型別碼), 請考慮使用Replace Type Code with Class;若是這個魔法數表明一個數組的長度,請在遍歷該數組的時候,改用Array.length()。

 

10. Encapsulate Field(封裝值域)

能理解和第一條「自封裝值域」的區別嗎?

應用場景:面向對象的首要原則之一就是封裝(encapsulation),或者稱爲「數據隱藏」(data hidding)。按此原則,你毫不應該將數據聲明爲public,不然其餘對象就有可能訪問甚至修改這項數據,而擁有該數據的對象卻毫無察覺。這就將數據和行爲分開了(不妙)。public 數據被看作是一種很差的做法,由於這樣會下降程序的模塊化程度(modularity)。若是數據和使用該數據的行爲被集中在一塊兒,一旦狀況發生變化,代碼的修改就會比較簡單,由於須要修改的代碼都集中於同一塊地方,而不是星羅棋佈地散落在整個程序中。Encapsulate Field是封裝過程的第一步。經過這項重構手法,你能夠將數據隱藏起來,並提供相應的訪問函數(accessors)。但它畢竟只是第一步。若是一個class除了訪問函數(accessors)外不能提供其餘行爲,它終究只是一個dumb class (啞類〕。這樣的class並不能得到對象技術的優點,而你知道,浪費任何一個對象都是很很差的。實施Encapsulate Field以後,我會嘗試尋找那些使用「新建 訪問函數」的函數,看看是否能夠經過簡單的Move Method 輕快地將它們移到新對象去。

 

11. Encapsulate Collection(封裝羣集)

應用場景:class經常會使用羣集(collection,多是array、list、set或vector)來保存一組實體。這樣的class一般也會提供針對該羣集的「取值/設值函數」(getter/setter)。可是,羣集的處理方式應該和其餘種類的數據略有不一樣。取值函數(getter)不應返回羣集自身,由於這將讓用戶得以修改羣集內容而羣集擁有者卻一無所悉。這也會對用戶暴露過多「對象內部數據結構」的信息。若是一個取值函數(getter)確實須要返回多個值,它應該避免用戶直接操做對象內所保存的羣集,並隱藏對象內「與用戶無關」的數據結構。至於如何作到這一點,視你使用的版本不一樣而有所不一樣。另外,不該該爲這整個羣集提供一個設值函數(setter),但應該提供用覺得羣集添加/移除(add/remove)元素的函數。這樣,羣集擁有者(對象)就能夠控制羣集元素的添加和移除。若是你作到以上數點,羣集(collection)就被很好地封裝起來了,這即可以下降羣集擁有者(class)和用戶之間的耦合度。

 

12. Replace Record with Data Class(以數據類取代記錄)

應用場景:Record structures (記錄型結構)是許多編程環境的共同性質。有一些理由使它們被帶進面向對象程序之中:你可能面對的是一個老舊程序( legacy program ),也可能須要經過一個傳統API 來與structured record 交流,或是處理從數據庫讀出的 records。這些時候你就有必要建立一個interfacing class ,用以處理這些外來數據。最簡單的做法就是先創建一個看起來相似外部記錄(external record)的class ,以便往後將某些值域和函數搬移到這個class 之中。一個不太常見但很是使人注目的狀況是:數組中的每一個位置上的元素都有特定含義,這種狀況下你應該使用Replace Array with Object 。

 

13. Replace Type Code with Class(以類取代型別碼)

應用場景:在以C 爲基礎的編程語言中,type code(型別碼)或枚舉值(enumerations)很常見。若是帶着一個有意義的符號名,type code 的可讀性仍是不錯的。問題在於,符號名終究只是個別名,編譯器看見的、進行型別檢驗的,仍是背後那個數值。任何接受type code 做爲引數(argument)的函數,所指望的其實是一個數值,沒法強制使用符號名。這會大大下降代碼的可讀性,從而成爲臭蟲之源。若是把那樣的數值換成一個class ,編譯器就能夠對這個class 進行型別檢驗。只要爲這個class 提供factory methods ,你就能夠始終保證只有合法的實體纔會被建立出 來,並且它們都會被傳遞給正確的宿主對象。可是,在使用Replace Type Code with Class 以前,你應該先考慮type code 的其餘替換方式。只有當type code 是純粹數據時(也就是type code 不會在switch 語句中引發行爲變化時),你才能以class 來取代它。Java 只能以整數做爲switch 語句的「轉轍」依據,不能使用任意class ,所以那種狀況下不可以以class 替換type code 。更重要的是:任何switch 語句都應該運用 Replace Conditional with Polymorphism 去掉。爲了進行那樣的重構,你首先必須運用 Replace Type Code with Subclasses 或Replace Type Code with State/Strategy 把type code處理掉。即便一個type code 不會因其數值的不一樣而引發行爲上的差別,宿主類中的某些行爲仍是有可能更適合置放於type code class 中,所以你還應該留意是否有必要使用Move Method 將一兩個函數搬過去。

 

14. Replace Type Code with Subclasses(以子類取代型別碼)

應用場景:若是你面對的type code 不會影響宿主類的行爲,你可使用Replace Type Code with Class 來處理它們。但若是type code 會影響宿主類的行爲,那麼最好的辦法就是藉助多態(polymorphism )來處理變化行爲。通常來講,這種狀況的標誌就是像switch 這樣的條件式。這種條件式可能有兩種表現形式:switch 語句或者if-then-else 結構。不論哪一種形式,它們都是檢查type code 值,並根據不一樣的值執行不一樣的動做。這種狀況下你應該以Replace Conditional with Polymorphism 進行重構。但爲了可以順利進行那樣的重構,首先應該將type code 替換爲可擁有多態行爲的繼承體系。這樣的一個繼承體系應該以type code 的宿主類爲base class,並針對每一種type code 各創建一個subclass 。爲創建這樣的繼承體系,最簡單的辦法就是Replace Type Code with Subclasses:以type code 的宿主類爲base class,針對每種type code 創建相應的subclass 。 可是如下兩種狀況你不能那麼作:(1) type code 值在對象建立以後發生了改變;(2)  因爲某些緣由,type code 宿主類已經有了subclass 。若是你剛好面臨這兩種狀況之一,就須要使用Replace Type Code with State/Strategy 。Replace Type Code with Subclasses 的主要做用實際上是搭建一個舞臺,讓Replace Conditional with Polymorphism 得以一展身手。若是宿主類中並無出現條件式,那麼 Replace Type Code with Class 更合適,風險也比較低。使用Replace Type Code with Subclasses 的另外一個緣由就是,宿主類中出現 了「只與具有特定type code 之對象相關」的特性。完成本項重構以後,你可使用 Push Down Method 和 Push Down Field 將這些特性推到合適的subclass去,以彰顯它們「只與特定狀況相關」這一事實。Replace Type Code with Subclasses 的好處在於:它把「對不一樣行爲的瞭解」從class 用戶那兒轉移到了class 自身。若是須要再加入新的行爲變化,我只需添加subclass 一個就好了。若是沒有多態機制,我就必須找到全部條件式,並逐一修改它們。所以,若是將來還有可能加入新行爲,這項重構將特別有價值。

 

15. Replace Type Code with State/Strategy(以State/strategy 取代型別碼)

應用場景:本項重構和Replace Type Code with Subclasses 很類似,但若是「type code 的值在對象生命期中發生變化」或「其餘緣由使得宿主類不能被subclassing 」,你也可使用本重構。本重構使用State 模式或Stategy 模式[Gang of Four]。State 模式和Stategy 模式很是類似,所以不管你選擇其中哪個,重構過程都是相同的。「選擇哪個模式」並不是問題關鍵所在,你只須要選擇更適合特定情境的模式就好了。若是你打算在完成本項重構以後再以 Replace Conditional with Polymorphism 簡化一個算法,那麼選擇Stategy 模式比較合適;若是你打算搬移與狀態相關(state-specific)的數據,並且你把新建對象視爲一種變遷狀態 (changing state),就應該選擇使用State 模式。

 

16. Replace Subclass with Fields(以值域取代子類)

應用場景:創建subclass 的目的,是爲了增如新特性,或變化其行爲。有一種變化行爲(variant behavior )稱爲「常量函數」(constant method)[Beck],它們會返回一個硬編碼 (hard-coded)值。這東西有其用途:你可讓不一樣的subclasses 中的同一個訪問函數(accessors)返回不一樣的值。你能夠在superclass 中將訪問函數聲明爲抽象函數, 並在不一樣的subclass 中讓它返回不一樣的值。儘管常量函數有其用途,但若subclass 中只有常量函數,實在沒有足夠的存在價值。 你能夠在中設計一個與「常量函數返回值」相應的值域,從而徹底去除這樣的subclass 。如此一來就能夠避免因subclassing 而帶來的額外複雜性。

相關文章
相關標籤/搜索