3.10Switch Statement
面向?qū)ο蟮囊粋€最明顯特征就是:少用switch或case語句。從本質(zhì)上講,switch語句的問題在于重復(fù)。通常用多態(tài)來解決Switch 語句帶來的壞味道。switch語句常常根據(jù)type code進行選擇,你要的是與該type code相關(guān)的函數(shù)或class。所以你應(yīng)該使用Extract Method將switch語句提煉到一個獨立的函數(shù)中,再以Move Method將他搬移到需要多態(tài)的那個class里頭。此時你必須決定是否使用Replace Type Code with Subclass或Replace Type Code with State/Strategy。一旦這樣完成繼承結(jié)構(gòu)之后,你就可以Replace Conditional with Polymorphism了。
如果你只是在單一函數(shù)中有些選擇事例,而你并不想改動它們,那么“多態(tài)”就有點殺雞用牛刀了。這種情況下Replace Parameter with Explicit Method是個不錯的選擇。如果你的選擇條件之一是null,可以試試Introduce Null Object。
3.11Parallel Inheritance Hierarchies(平行繼承關(guān)系)
在這種情況下,每當(dāng)你為某個class增加一個subclass,必須也為另外一個class相應(yīng)增加一個subclass。如果你發(fā)現(xiàn)某個繼承體系的class名稱前綴和另一個繼承體系的class名稱前綴完全相同,便是聞到了這種壞味道。消除這種重復(fù)性的一般策略是:讓一個繼承體系的實體(instance)指涉(參考、引用、refer to)另一個繼承體系的實體。如果再接再厲運用Move Method和Move Field,就可以將指涉端的繼承實體消弭。
3.12Lazy Class(冗贅類)
你所創(chuàng)建的每一個class,都得有人去理解它、維護它,這些工作都是要花錢的。如果一個class的所得不值其身價,它就應(yīng)該消失。項目中經(jīng)常會出現(xiàn)這樣的情況:某個class原本對得起自己的身價,但重構(gòu)使它身形縮水,不再做那么多工作;或開發(fā)者事前規(guī)劃了某些變化,并添加一個class來對付這些變化,但變化實際上沒有發(fā)生。如果某些subclass沒有做滿足夠工作,試試Collapse Hierarchy。對于幾乎沒用的組件,你應(yīng)該以inline class對付它們。
Collapse Hierarchy(折疊繼承體系)
superclass和subclass之間無太大區(qū)別。
將它們合為一體。
動機
如果你曾經(jīng)寫過繼承體系,你就會知道,繼承體系很容易變得過分復(fù)雜。所謂重構(gòu)繼承體系,往往是將函數(shù)和值域在體系中上下移動。完成這些動作后,你很可能發(fā)現(xiàn)某個subclass并未帶來該有的價值,因此需要把classes合并(折疊)起來。
作法:
選擇你想移除的class:是superclass還是subclass?
使用pull up Field和pull up method或者push down method和push down field,把想要移除的class內(nèi)的所有行為和數(shù)據(jù)搬移到另一個class。
每次移動后,編譯并測試。
調(diào)整“即將被移除的那個class”的所有引用點,令它們改而引用合并(折疊)后留下的class。這個動作將會影響到變量的聲明、參數(shù)的型別以及構(gòu)造函數(shù)。
移除我們的目標(biāo);此時它應(yīng)該已經(jīng)成為一個空類。
編譯、測試。
3.13Speculative Generality(夸夸其談未來性)
如果所有的裝置都會被用到,那就值得處理這些非必要的事情;如果用不到就不值得。用不上的裝置只會擋你的路,所以,把它搬開吧。
如果函數(shù)或class的唯一用戶是test cases(測試用例),這就飄出了壞味道。如果你發(fā)現(xiàn)這樣的函數(shù)或class,請把它們連同其test case都刪掉。但如果他們的用途是幫助test cases檢測正當(dāng)功能,當(dāng)然必須刀下留人。
3.14Temporary Field(令人迷惑的暫時值域)
有時你會看到這樣的對象:其內(nèi)某個instance變量僅為某種特定情勢而設(shè)。這樣的代碼讓人不易理解,因為你通常認(rèn)為對象在所有時候都需要它的所有變量。在變量未被使用的情況下猜測起當(dāng)初設(shè)置的目的,會讓你發(fā)瘋。
請使用Extract Class給這個可憐的孤兒創(chuàng)造一個家,然后把所有和這個變量相關(guān)的代碼都放進這個新家。
如果class中有一個復(fù)雜算法,需要好幾個變量,往往就可能導(dǎo)致這種壞味道。由于實現(xiàn)者不希望傳遞一長串參數(shù),所以他把這些參數(shù)都放進值域中,但是這些值域只在使用該算法的時候才有效,其他情況下只會讓人迷惑。這個時候可以用Extract Class把這些變量和其相關(guān)的函數(shù)提煉到一個獨立的class中,提煉后的新對象是一個method object。
3.15Message Chains(過渡耦合的消息鏈)
用戶索求對象,然后向求得的對象繼續(xù)索求對象。
這時候應(yīng)該使用Hide Delegate。應(yīng)該先觀察Message Chain最終得到的對象是用來干什么的,看看能否以Extract Method把使用該對象的代碼提煉到一個獨立函數(shù)中,再運用Move Method把這個函數(shù)推入Message Chain。
Hide Delegate(隱藏委托關(guān)系)

當(dāng)類圖如上所示時,客戶端如果想要獲得對應(yīng)Person的Manager,需要如下過程:
Department department = person.Department;
Manager manager = department.Manager;
通過重構(gòu),為類Person添加屬性Manager,如:
1 public class Person
2 {
3 private Department _department;
4 public Department Department
5 {
6 get
7 {
8 return _department;
9 }
10 set
11 {
12 _department = value;
13 }
14 }
15 public Manager Manger
16 {
17 get
18 {
19 return _department.Manager;
20 }
21 }
22 }
這樣客戶端可以這樣寫:
Manager manager = person.Manger;
當(dāng)然如果person不屬于任何一個部門,那么程序就會出錯。這里其實可以引入Null Object來解決問題。
3.16Middle Man(中間轉(zhuǎn)手人)
人們可能會過渡運用delegation。你也許會看到某個class所實現(xiàn)的接口有一半的函數(shù)都委托給其他class,這樣就使過渡運用。這時你應(yīng)該使用Remove Middle Man,直接和職責(zé)對象打交道。如果這樣“不干實事”的函數(shù)只有少數(shù)幾個,可以運用inline Method把它們放進調(diào)用端。如果這些Middle Man 還有其他的行為,你可以運用Replace Delegation with Inheritance把它們變成實責(zé)對象的subclass,這樣你既可以擴展原對象的行為,又不必負(fù)擔(dān)那么多的委托動作。
Remove Middle Man
這個重構(gòu)過程和Hide Delegate剛好相反。
Replace Delegation with Inheritance
這個重構(gòu)過程就是把關(guān)聯(lián)關(guān)系轉(zhuǎn)換為繼承關(guān)系。
即

轉(zhuǎn)變?yōu)?br>

3.17Inappropriate Intimacy
當(dāng)兩個類彼此關(guān)系緊密地時候,有可能是他們所包含的方法或值域處在錯誤的類中而導(dǎo)致,此時應(yīng)當(dāng)通過Move Method或Move Field把它們移動到合適的位置。應(yīng)當(dāng)看一下兩個類之間是否真的需要雙向?qū)Ш疥P(guān)系,如果不是,讓它們之間的關(guān)系變?yōu)閱蜗驅(qū)Ш疥P(guān)系。或者通過Extract Class把共同的函數(shù)或值域抽取出來,讓每一個Class直接去調(diào)用被抽取出來的類。另外如果是子類和父類過分耦合,則可以通過將繼承轉(zhuǎn)換為關(guān)聯(lián)的手段來降低耦合性。
3.18Alternative Classes with Different Interfaces(異曲同工的類)
如果兩個函數(shù)做同一件事,卻有著不同的簽名式,應(yīng)當(dāng)運用Rename Method根據(jù)它們的用途重新命名。請反復(fù)運用Move Method將某些行為移入classes,直到兩者的協(xié)議一致為止。或許可以使用Extract Superclass來解決問題。
3.19Incomplete Library Class(不完美的程序庫類)
當(dāng)你面對的程序類庫不夠好( 或者不能滿足要求)而你又不能直接改變它時,可以采用Introduce Foreign Method 方法來修改其中一兩個函數(shù),或者使用Introduce Local Extension來添加行為。
Introduce Foreign Method (162)
在client class建立一個函數(shù),并以一個server class實體作為第一個參數(shù)。書中的例子:

Introduce Local Extension(164)
這種重構(gòu)方式實際上就是從已封裝的類庫中繼承你認(rèn)為功能不足的那個類,然后添加你想要實現(xiàn)的功能。當(dāng)然,有時候你想擴展的那個類碰巧加上了關(guān)鍵字final(java) 或sealed(c#),這樣就沒辦法了。
3.20Data Class(純稚的數(shù)據(jù)類)
所謂Data Class是指:它們擁有一些值域,以及用于訪問這些值域的函數(shù),除此之外一無長物。早期的Data Class可能會包含public值域或容器類值域,應(yīng)當(dāng)首先將它們封裝起來(用屬性或特定方法)。盡量可能的把外界操縱這些值域的方法搬移到類里面來,那些不應(yīng)該被其他classes修改的值域,請運用Remove Setting Method。
Remove Setting Method
即去調(diào)設(shè)值函數(shù)或?qū)傩灾械膕et讓它變?yōu)橹蛔x。
3.21Refused Bequest
當(dāng)子類不愿意繼承父類的某些東西時,建議將繼承修改為關(guān)聯(lián)。不建議隨便修改繼承體系。這是一種很淡的壞味道。
3.22Comments(過多的注釋)
注釋本身并不是壞味道,它可以幫助我們找到代碼中的壞味道。
如果你需要注釋來解釋一塊代碼做了什么,試試Extract Method;如果method已經(jīng)提煉出來,但還是需要注釋來解釋其行為,試試Rename Mehtod;如果你需要注釋說明某系系統(tǒng)的需求規(guī)格,試試Introduce Assertion。
當(dāng)你感覺需要撰寫注釋,請先嘗試重構(gòu),試著讓所有注釋都變得多余。
如果你不知道該做什么,這才是注釋的良好運用時機。除了用來記述將來的打算之外,注視還可以用來標(biāo)注你并無十足把握的區(qū)域。你可以在注釋里寫下“為什么做某某事”。這類信息可以幫助將來的修改者,尤其是那些健忘的家伙。
Introduce Assertion
引入斷言。
某一段代碼需要對程序狀態(tài)作出某種假設(shè)。注意,你可以創(chuàng)建自己的斷言,而不僅僅是依賴已有斷言。
posted on 2007-07-30 22:11
littlegai 閱讀(411)
評論(0) 編輯 收藏 引用 所屬分類:
我的讀書筆記