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

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

更復(fù)雜的情況:

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