#
如果兩個函數做同一件事,卻有著不同的簽名式(signature),請運用Rename Method(273)根據它們的用途重新命名。但往往不夠,請反復運用Move Method(142)將某些行為移入classes,直到兩者的協議(protocols)一致為止。如果你必須重復而贅余地移入代碼才能完成這些,或許可運用Extract Superclass(336)為自己贖點罪。
有時你會看到兩個classes過于親密,花費太多時間去探究彼此的private成分。如果這發生在兩個[人]之間,我們不必做衛道之士;但對于classes,我們希望它們嚴守清規。
就像古代戀人一樣,過分狎昵的classes必須拆散。你可以采用Move Method(142)和Move Field(146)
幫它們劃清界線,從而減少狎昵行經。你可以看看是否運用Change Bidirectional Association to Unidirectional(200)讓其中一個class對另一個斬斷情絲。如果兩個classes實在是情投意合,可以運用Extract Class(149)把兩者共同點提煉到一個安全地點,讓它們坦蕩地使用這個新class。或者也可以嘗試運用Hide Delegate(157)讓另一個class來為它們傳遞相思情。
繼承(inheritance)往往造成過度親密,因為subclass對superclass的了解總是超過superclass的主管愿望。如果你覺得該讓這個孩子獨自生活了,請運用Replace Inheritance with Delegation(352)讓它離開繼承體系。
對象的基本特性之一就是封裝(encapsulation)-對外部世界隱藏其內部細節。封裝往往伴隨delegation(委托)。比如說你問主管是否
有時間參加一個會議,他就把這個消息委托給他的記事薄,然后才能回答你。很好,你沒必要知道這位主管到底使用傳統記事薄或電子記事薄抑或秘書來記錄自己的
約會。
但是人們可能過度運用delegation。你也許會看到某個class接口有一半的函數都委托給其他class,這樣就是過度運用。這時你應該使用Remove Middle Man(160),直接和實責對象打交道。如果這樣[不干實事]的函數只有少數幾個,可以運用Inline Method(117)把它們“inlining”,放進調用端。如果這些Middle Man還有其他行為,你可以運用Replace Delegation with Inheritance(335)把它變成實責對象的subclass,這樣你既可以擴展原對象的行為,又不必負擔那么多的委托動作。
如果你看到用戶向一個對象索求(request)另一個對象,然后再向后者索求另一個對象,然后再索求另一個對象。。。。。。這就是Message Chain。實際代碼中你看到的可能是一長串getThis()或一長串臨時變量。采取這種方式,意味客戶將與查找過程中的航行結構(structure of navigation)緊密耦合。一旦對象間的關系發生任何變化,客戶端就不得不作出相應修改。
這時候你應該使用Hide Delegate(157)。你可以在Message Chain的不同位置進行這種重構手法。理論上你可以重構Message Chain上的任何一個對象,但這么做往往會把所有中介對象(intermediate object)都變成Middle Man.通常更好的選擇是:先觀察Message Chain最終得到的對象是用來干什么的,看看能否以Extract Method(110)把使用該對象的代碼提煉到一個獨立函數中,再運用Move Method(142)把這個函數推入Message Chain。如果這條鏈上的某個對象有多位客戶打算航行此航線的剩余部分,就加一個函數來做這件事。
有時你會看到這樣的對象:其內某個instance變量僅為某種特定情勢而設。這樣的代碼讓人不易理解,因為你通常認為對象在所有時候都需要它的所有變量。在變量未被使用的情況下猜測當初其設置目的,會讓你發瘋。
請使用Extract Class(149)給這個可憐的孤兒創造一個家,然后把所有和這個變量相關的代碼都放進這個新家。也許你還可以使用Introduce Null Object(260)在[變量不合法]的情況下創建一個null對象,從而避免寫出[條件式代碼]。
如果class中有一個復雜算法,需要好幾個變量,往往就可能導致壞味道Temporary Field的出現。由于實現者不希望傳遞一長串參數(想想為什么),所以他把這些參數都放進值域(fields)中。但是這些值域只在使用該算法時才有效,其他情況下只會讓人迷惑。這時候你可以利用Extract Class(149)把這些變量和其相關函數提煉到一個獨立class中。提煉后的新對象將時一個method object[Beck](譯注:其存在只是為了提供調用函數的途徑,class本身并無抽象意味)。
當有人說[噢,我想我們總有一天需要做這事]并因而企圖以各式各樣得掛鉤(hooks)和特殊情況來處理一些非必要的事情,這種壞味道就出現了。那么做的
結果往往造成系統更難理解和維護。如果所有裝置都會被用到,那就值得那么做;如果用不到,就不值得。用不上得裝置只會擋你得路,所以,把它搬開吧。
如果你的某個abstract class其實沒有太大作用,請運用Collapse Hierarchy(334)。非必要之delegation(委托)可運用Inline Class(154)除掉。如果函數的某些參數未被用上,可對它實施Remove Parameter(277)。如果函數名稱帶有多余的抽象意味,應該對它實施Rename Method(273)讓它實現一些。
如果函數或class的唯一用戶是test cases(測試用例),這就飄出了壞味道Speculative Generality。如果你發現這樣的函數或clss,請把它們連同其test cases都刪掉。但如果它們的用途是幫助test cases檢測正當功能,當然必須刀下留人。
項目中經常會出現這樣的情況:某個class原本對得起自己得身價,但重構使它身形縮水,不再做那么多工作;或開發者事前規劃了某些變化,并添加一個
class來應付這些變化,但變化實際上沒有發生。不論上述哪一種原因,請讓這個class莊嚴赴義吧。如果某些subclass沒有做滿足夠工作,試試Collapse Hierarchy(344)。對于幾乎沒用得組件,你應該以Inline Class(154)對付它們。
Parallel Inheritance Hierachies其實是Shotgun Surgery的特殊情況。在這種情況下,每當你為某個class增加一個subclass,必須也為另一個class相應增加一個subclass。如果你發現某個繼承體系的class名稱前綴和另一個繼承體系的class名稱前綴完全相同,便是聞到了這種味道。
消除這種重復性的一般策略是:讓一個繼承體系的實體(instances)指涉(參考、引用、refer to)另一個繼承體系的實體(instances)。如果再接再厲運用Move Method(142)和Move Field(146),就可以將指涉端(referring class)的繼承體系消弭于無形。
面向對象程序的一個最明顯特征就是:少用switch(或case)語句。從本質上說,switch語句的問題在于重復(duplication)。你常
會發現同樣的switch語句散布于不同地點。如果要為它添加一個新的case子句,你必須找到所有switch語句并修改它們。面向對象中的多態
(ploymorphism)概念可為此帶來優雅的解決辦法。
大多數時候,一看到switch語句你就應該考慮以[多態]來替換它。問題是多態該出現在哪兒?switch語句常常根據type code(型別碼)進行選擇,你要的是[與該type code相關的函數或class]。所以你應該所用Extract Method(110)將switch語句提煉到一個獨立函數中,再以Move Method(142)將它搬移到需要多態性的那個class里頭。此時你必須決定是否使用Replace Type Code with Subclasses(223)或Replace Type Code with State/Strategy(227)。一旦這樣完成繼承結構之后,你就可以運用Replace Conditional with Polymorphism(225)了。
如果你只要在單一函數中有些選擇事例,而你并不想改動它們,那么[多態]就有點殺雞用牛刀了。這種情況下Replace Parameter with Explicit Methods(285)是個不錯的選擇。如果你的選擇條件之一是null,可以試試Introduce Null Object(260)。
大多數編程環境都有兩種數據:結構型別(record types)允許你將數據組織成有意義的形式;基本型別(primitive
types)則是構成結構型別的積木塊。結構總是會帶來一定的額外開銷。它們有點像數據庫中的表格,或是那些得不償失(只為做一兩件事而創建,卻付出太大
額外開銷)的東西。
對象技術的新手通常不原意在小任務上運用小對象-像是結合數值和幣別的money class、含一個起始值和一個結束值的range class、電話號碼或郵政編碼(zip)等等的特殊Strings。你可以運用Replace Data Value with Object(175)將原本單獨存在的數據值替換為對象,從而走出傳統的洞窟,進入炙手可熱的對象世界。如果欲替換之數據值是type code(型別碼),而它并不影響行為,你可以運用Replace Type Code with Class(218)將它換掉。如果你有相依于此type code的條件式,可運用Replace Type Code with Subclass(227)或Replace Type Code with State/Strategy(227)加以處理。
如果你有一組應該總是被放在一起的值域(fields),可運用Extract Class(149)。如果你在參數列中看到基本型數據,不妨試試Introduce Parameter Object(295)。如果你發現自己正從array中挑選數據,可運用Replace Array with Object(186)。
|