接着昨天的繼續說,廢話不說,直接進入正題:
【3】Large Class(過大的類)
查看一個類是否“過大”,這里有一個小技巧分享給大家。就是看兩點:1)這個類實例變量太多,必然會有Duplicated Code(重復代碼) 2)類內如果有太多代碼,也會產生Duplicated Code,讓整個類看起來混亂並最終走向死亡。
因此當你察覺到這個類是一個Large Class的時候,重構的信號就來了。對於實例變量太多來說,你可以查看尋找那些彼此相關的變量,或者是他們的命名的前綴或者后綴是相同的變量,你可以通過Extract Class把他們移到別的組件中去。如果這個組件你感覺更加是否作為本類的子類,那么你可以運用Extract Subclass來進行提煉。
如果你發現有些時候,類中並非在所有時刻都使用所有實例變量,那么你可以多次使用Extract Class或者Extract Subclass。文章作者提供了一個提煉的小技巧,你如果不知道如何提煉分解這個類,你可以去查看客戶端是如何使用他們的,然后通過Extract Interface提煉接口,借助於這些提煉接口,可以幫助你知道如何更好的分解這個過大類。
如果你的過大類是一個GUI類,那么你可能需要將數據和行為移動到一個獨立的領域中去。並且你需要GUI和數據行為兩者的數據保持同步。那么你可以使用Duplicate Observed Data來進行提煉。
【4】Long Parameter List(過長參數列)
在對象技術出來之前,函數的參數列表往往是又臭又長,然而有時候你如果不想要這種長參數列的函數你得去依靠全局變量這種邪惡的東西。而且在我們一開始學習編程的時候老師就教導我們,函數需要什么你就在參數列表中寫什么。是時候需要改變了,對象技術的出現提供了我們改變這一現狀的手段。你不再需要傳過長過大的參數列,因為太多參數往往會造成參數前后不一致,不易使用,更重要的是一旦你需要更多的數據,你就不得不去修改它。相反如果你通過傳入對象,首先你的參數列表就很短,其次如果你想增加別的變量,會有可能只需要在函數中對這個參數對象多加一次請求就行了。
如果向已有的對象發送一條請求可以取代一個參數,那么你應該使用Replace Parameter with Method。注意是已有的參數,不是不存在的參數。這個需要理解一下,已有的參數就是函數宿主類中的某一個對象字段,也可能是函數本身存在另一個對象參數,讓這個對象來替換它。如果某些數據缺乏合理的對象歸屬。可以使用Introduce Parameter Object來為它們制造一個“參數對象”。
有些同學可能會和我對這條有同樣的疑問,有些時候我們不想增加對象與對象之間的關聯。不想讓被調用對象與較大對象之間有某種依賴關系。這個時候將數據從對象拆解出來單獨作為參數也合情合理。但如果你此時函數的參數列表過長或者變化太頻繁,你確實應該采取本手法來進行重構。
【5】Divergent Change(發散式變化)
我們需要軟件更容易被修改。遇到修改,我們希望只跳到系統的某一點,只在該處做出修改就行。但情況往往沒有這么簡單,因為總有那么幾個類變化的原因往往是多個,他們經常會因為不同的原因在不同的方向上都要變化,都要做出適當修改。這個時候你就要注意了,比如你看到一個類說如果加入一個數據庫,我需要修改其中的三個函數。如果加入一個金融工具,我需要修改其中的四個函數。面對這兩個不同工具的加入,你有2個方向上的變化。那么其實你更應該用Extract Class將這個類分成兩個類,每個類只針對一個變化原因進行變化,就比如類A只對數據庫進行變化,類B只對金融工具發生變化。為此,你應該找出某特定原因而發生的所有變化,然后Extract到別的class中去。時刻要記住這么一句話:針對某一外界變化的所有相應修改,都應該產生在單一類中,而這個新類中的所有內容都應該反應此變化。
【6】Shotgun Surgery(散彈式修改)
情況與Divergent Change類似,但不同點在於,Divergent Change是指一個類受多種變化的影響,而Shotgun Surgery表示針對某一變化,你都必須在不同類做出相應的修改。針對這種情況你需要使用Move Mehod和Move Field將需要修改的代碼放進同一個類中,如果眼下沒有合適的類的話,就創建一個。通常你也可以使用Inline Class把一系列相關行為放進同一個類,這可能會造成少量Divergent Change,但你可以輕易處理它。
Shotgun Surgery和Divergent Change你都需要適時整理重構代碼,讓“外界變化”和“需要修改的類”趨於一一對應。
【7】Feature Envy(依賴情結)
面向對象技術就是將數據和行為包裝在一起。一個經典的壞味道的場景就是函數都某個類的興趣高過對自己所處類的興趣,往往焦點就是數據。很多時候我們可以看到這種場景,類A的中的函數為了進行計算獲取了類B中幾乎一半的數據,面對這種情況,其實很簡單,就是使用Move Method將這個函數直接移到B中去,然后讓類A的調用點就調用類B的這個函數。如果一個函數中,只有一部分受這種“依戀之苦”,你應該用Extract Method把這一部分提煉出來,然后通過Move Method把這個提煉的函數移動到他所依戀的類中去。
如果出現一個函數需要用到幾個類的時候,我們會很難判斷究竟應該把它放哪。這個時候有個小技巧你只要記住,這個函數獲取哪個類的數據最多,就把這個函數移動到哪個類中去。當然面對這種多重以來,你也可以用Extract Method將這個函數分解成一系列小函數然后移動到他們對應的需要的類中去也可以輕松完成。
文中作者也提到了設計模式GoF也有破壞這個規則的時候,我們一起來看下:Stategy和Visitor,他們通過一個間接層,將實現委托給了Stategy和Visitor,而不是直接去訪問這個間接層,這樣,原來函數的數據就與真正需要他們的Stategy和Visitor中的行為分開了。那么他們的目的是什么?可以說這兩個模式主要是為了解決Divergent Change而設計的。總之最根本的原則就是:將總是一起變化的東西放在一塊兒,數據和引用這些數據的行為總是一起變化的。但也有例外,如果例外出現,我們就搬移動那些行為,保持變化只在一個地方發生,Strategy和Visitor使你得以輕松修改函數行為,因為他們將少量需要被覆寫的行為隔離開來,當然也付出了“多一層間接性”的代價。