3.5 Divergent Change ( 發散式變化)
針對某一外界變化的所有相應修改,都只應該發生在單一class中,而這個新class內的所有內容都應該反映外界變化。為此,你應該找出因為某特定原因而造成的所有變化,然后運用Extract Class將他們提煉到另一個class中。
3.6Shotgun Surgery(散彈式修改)
如果每遇到某種變化,你都必須在許多不同的classes內作出許多小修改以響應之,你所面臨的壞味道就是散彈式修改。
這種情況下,你應該使用Move Method和Move Field把所有需要修改的代碼放進同一個class。如果眼下沒有合適的class可以安置這些代碼,就創造一個。通常你可以運用Inline Class把一系列相關行為放進同一個class。這可能會造成少量發散式變化,但你可以輕易處理它。
InLine Class
場景:你的某個class沒有做太多事情(沒有承擔足夠責任)。
將class的所有特性搬移到另一個class中,然后移除原class。
作法
在移動的目的class身上聲明所有被移動的class的公共屬性和方法。(如果“以一個獨立接口表示source class函數”更合適的話,就應該在inlining之前使用Extract Interface)
修改所有source class引用點,改而引用吸收被移動的class的目標class。(將source class聲明為private,以斬斷package之外的所有引用可能。同時并修改source class的名稱,這便可以使編譯器幫助你捕獲到所有對于source class的dangling reference(虛懸引用點)
編譯,測試
運用Move Method和Move Field,將source class的特性全部搬移到目標class。
3.7Feature Envy(依戀情結)
有一種經典的氣味是:函數對某個class的興趣高過對自己所處之host class的興趣。這種傾慕之情最通常的焦點便是數據。無數次經驗里,我們看到某個函數為了計算某值,從另一個對象那兒調用幾乎半打的取值函數。療法顯而易見:把這個函數移至另一個地點。你應該使用Move Method,把它移到它該去的地方。有時候函數只有一部分受這種依戀之苦,這時候你應該使用Extract Method把這一部分提煉到獨立的函數中,再使用Move Method帶它去它的夢中家園。
往往一個函數會用上數個classes特性。那么它究竟該被置于何處呢?我們的原則是:判斷哪個class擁有最多“被此函數使用”的數據,然后就把這個函數和那些數據擺在一起。如果先以Extract Method將這個函數分解為數個較小函數并分別放置于不同地點,上述步驟也就比較容易完成了。
3.8Data Clumps(數據泥團)
將“總是綁在一起出現的數據”放進屬于它們自己的對象中。首先找出這些數據的值域形式出現點,運用Extract Class將它們提煉到一個獨立的對象中。然后運用Introduce Parameter Object 或 Preserve Whole Object為參數過長的成員函數減肥。
一個好的評判數據泥團的辦法:刪除掉眾多數據中的一筆。其他數據如果不再有意義,這就是個明確的信號:你應該為它們產生一個新對象。
3.9Primitive Obsession(基本型別偏執)
對象技術的新手通常不愿意在小任務上運用小對象--像是結合數值和幣值的money class、含一個起始值和一個結束值得range class、電話號碼或郵政編碼(ZIP)等等的特殊strings。你可以運用Replace Data Value with Object將原本單獨存在的數值替換為對象。如果欲替換之數值是type code(型別碼),而它不影響行為,你可以運用Replace Type Code with Class將它換掉。如果你有相依于此type code的條件式,可運用Replace Type Code with SubClass或Replace Type Code with State/Strategy 加以處理。
如果你有一組應該總是放在一起的值域,可運用Extract Class。如果你在參數列中看到基本型數據,不妨試試Introduce Parameter Object。如果你發現自己正在從array中挑選數據,可運用Replace Array with Object。
Replace Type Code with Class
在以C為基礎的編程語言中,type code(型別碼)或枚舉值很常見。如果帶著一個有意義的符號名,type code的可讀性還是不錯的。問題在于,符號名終究只是個別名,編譯器看見的、進行型別檢驗的,還是背后那個數值。任何接受type code作為引數(argument)的函數,所期望的實際上是一個數值,無法強制使用符號名。這會大大降低代碼的可讀性,從而成為臭蟲之源。
如果把那樣的數值轉換為一個class,編譯器就可以對這個class進行型別檢驗。只要為這個class提供factory method,你就可以始終保證只有合法的實體才會被創建出來,而且它們都回被傳遞給正確的宿主對象。
但是如果枚舉被switch所使用,你就不能簡單地將它替換為class。因為switch往往只會接受整型數據。所以應該首先對switch運用Replace Conditional with Polymorphism重構,而在這之前,還要進行Replace Type Code with Subclasses或Replace Type Code with State/Strategy把type code處理掉。
作法:
1、為 type code建立一個class。其中包含相應的用于記錄type code的值域,并準備一組static變量保存允許被創建的實體,并以一個static函數根據原本的type code 返回合適的實體。
2、修改source class代碼,讓他使用上述新建的class。
3、編譯、測試
4、對于source class中每一個使用type code 的函數,相應建立一個函數,讓新函數使用新的class。你需要建立“以新class實體為自變量”的函數,用以替換原先“直接以type code為引數”的函數。你還需要建立一個“返回新class實體”的函數,用以替換原先一直返回type code的函數。建立新函數前,你可以使用Rename Method修改原函數名稱,明確指出那些函數仍然使用舊式的type code。
5、逐一修改source class用戶,讓它們使用新接口。
6、每修改一個用戶,編譯并測試。
7、刪除使用“type code”的接口,并刪除保存舊type code的靜態變量。
8、編譯測試。
Replace Type Code with SubClass
我的理解:

在這里Room類包含一個屬性IsMeetingRoom。如果為true,則這個房間就是一個會議室,如果為false,則這個房間就是一個普通的房間。這里true和false可以理解為一個簡單的“型別編碼”。它可以重構為下面的形式:

更復雜的情況:

這樣就需要根據RoomType的實際數量分離出多個子類來。
作法:
使用Self Encapsulate Field方法把type code自我封裝起來。如果type code被傳遞給構造函數,你就需要將構造函數換成factory method.
為type code的每一個數值建立一個相應的subclass。在每個subclass中覆寫type code的取值函數(getter),使其返回相應的type code值。
每建立一個新的subclass,編譯并測試。
從superclass中刪除掉保存type code的值域。將type code訪問函數聲明為抽象函數。
編譯,測試。
Replace Type Code with State/Strategy
你有一個type code,它會影響class的行為,但你無法使用subclassing。
以state object(專門用來描述狀態的對象)取代type code。
之所以無法subclassing是因為Type Code會在對象生命周期過程中發生變化。
如果你打算再完成本項重構之后再以Replace Conditional with Polymorphism(用多態替換條件判斷)簡化一個算法,那么選擇Strategy模式比較合適;如果你打算搬移與狀態有關的數據,而且你把新建對象視為一種變遷狀態,就應該使用State模式。
Replace Array with Object
你有一個數組(array),其中的元素各自代表不同的東西。
以對象替換數組,對于數組中的每個元素,以一個值域表示之。
String[] row =new String[3];
row[0]="Liverpool";
row[1]="15";
轉換為
Performance row=new Performance();
row.setName("Liverpool");
row.setWins("15");
3.10Switch Statements
如果你只是在單一函數中有些選擇事例,而你并不想改動它們,那么“多態”就有點殺雞用牛刀了。這種情況下Replace Paremeter with Explicit Method是個不錯的選擇。如果你的選擇條件之一是null,可以試試Introduce Null Object
Replace Paremeter with Explicit Method
此方法即是將包含多個switch分支的函數的各個分支獨立成不同的函數。比如:
public static Object CreateObject(int typeOfObject)
{
switch(typeOfObject)
{
case 1:return new A();
case 2:return new B();
case 3:return new C();
}
}
轉換為:
public static Object Create1()
{
return new A();
}
public static Object Create2()
{
return new B();
}
public static Object Create3()
{
return new C();
}
思考:這樣會削弱此函數的運行期特性。
Introduce Null Object
使用這種重構方法的好處就是你不用在向一個對象發送消息的時候去檢驗這個對象是否存在。因為對象確實存在,只不過有可能對你發送的消息做出“我是空”的回應。
作法
為source class建立一個subclass,使其行為像source class的null版本。在source class和null class中都加上IsNull()函數,前者的IsNull()應該返回false,后者的應該返回true.可以建立一個nullable接口,將isnull函數放在其中,讓source class實現這個接口。
另外可以創建一個testing接口,專門用來檢驗對象是否為null.
編譯。
找出所有“索求source obejct卻得到一個null”的地方。修改這些地方,使他們改而獲得一個null object。
找出所有“講source object與null做比較的地方”。修改這些地方,使它們調用IsNull()函數。
你可以在不該在出現null value的地方放上一些斷言,確保null的確不再出現。這可能對你有所幫助。
這出這樣的程序點:如果對象不是null,做A動作,否則做B動作。
對于每一個上述地點,在null class中覆寫A動作,使其行為和B動作相同。
使用上述的被覆寫動作(A),然后刪除“對象是否等于null”的條件測試。編譯并測試。
其他特殊情況
使用本項重構時,你可以有數種不同的null objects,例如你可以說“沒有顧客”和“不知名顧客”這兩種情況是不同的。果真如此,你可以針對不同的情況建立不同的null class。有時候null objects 也可以攜帶數據,例如不知名顧客的使用記錄等等。
本質上講,這是一個比Null Object模式更大的模式:Special Case模式。所謂Special case class是某個class的特殊情況,有著特殊的行為。因此表述不知名顧客的UnKnownCustomer 和表示沒有顧客的NoCustomer都是Customer的特例。你經常可以在表示數量的class中看到這樣的特例類。例如Java浮點數有“正無窮大”、“負無窮大”和“非數量”等特例。special case class的價值是:它們可以降低你的錯誤處理開銷。例如浮點運算決不會拋出異常。如果你對NaN作浮點運算,結果也會是個NaN。這和“null object”的訪問函數通常會返回另一個null object是一樣的道理。
posted on 2007-07-17 21:26
littlegai 閱讀(290)
評論(0) 編輯 收藏 引用 所屬分類:
我的讀書筆記