重構第八章讀書筆記(下)

8.10 Encapsulate (封裝字段)
問題:你的類中存在一個public字段。
方法:將它生命爲private,並且提供相應的訪問函數。
動機:面向對象的首要原則就是封裝,所以不應該將數據聲明爲public,可能會有其他對象修改這個數據,這樣也會降低程序的模塊化程度,比如使用private的話,可以保證數據和相關的代碼比較集中,萬一出現問題,也方便維護。
做法:1.爲public字段提供取值/設值函數;2.找到這個類以外使用該字段的地點。如果客戶只是讀取該字段,就把引用替換爲對取值函數的調用,如果客戶更改了該字段的值,就將此引用點替換爲設值得函數;3.每次修改之後編譯測試;4.將字段的所有用戶修改完畢後,把字段聲明爲private;5.編譯測試。

8.11 Encapsulate Collection(封裝集合)
問題:有個函數返回一個集合。
方法:讓這個函數返回該集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數。
動機:取值函數不該返回集合自身,會讓用戶修改集合但是集合擁有者卻一無所知,另外不應該爲這個集合提供設值函數,但是應該提供爲集合添加/移除元素的函數。
做法:1.加入爲集合添加/移除元素的函數;2.將保存集合的字段初始化爲一個空集合;3.編譯;4.找出集合設值函數的所有調用者,你可以修改那個設值函數,讓它使用上述新建立的添加/移除元素函數,也可以直接修改調用端,改讓它們調用上述新建立的添加/移除元素函數;5.編譯測試;6.找出所有“通過取值函數調用並且修改其內容”的函數。逐一修改這些函數,讓他們改用添加/移除函數,每次修改後編譯測試。7.修改完上述所有的“通過取值函數獲取集合並修改集合內容”的函數後,修改取值函數自身,使它返回該集合的一個只讀副本。8.編譯測試;9.找出取值函數的所有用戶,從中找出應該存在於集合所屬對象內的代碼。運用Extract Method(110)和Move Method(142)將這些代碼移到宿主對象去;10.修改現有取值函數的名字,然後添加一個新的取值函數,使其返回一個枚舉,找出舊取值函數的所有被使用點,將它們都改爲使用新的取值函數;11.如果這一步跨度太大,可以先使用Rename Method(273)修改原取值函數的名稱,再建立一個新取值函數用以返回枚舉;最後再修改所有調用者,使其調用新的取值函數;12.編譯測試。

8.12 Replace Record with Data Class(以數據類取代記錄)
問題:你需要面對傳統編程環境中的記錄結構。
方法:爲該記錄創建一個“啞”數據對象。
動機:記錄型結構是許多編程環境的共同性質,有一些遺留程序,可能需要通過一個傳統API來與記錄結構交流,或者處理從數據庫讀出的記錄。
做法:1.新建一個類,表示這個記錄;2.對於記錄中的每一項數據,在新建的類中建立對應的一個private字段,並提供相應的取值/設置函數;

8.13 Replace Type Code With Class(以類取代類型碼)
問題:類之中有一個數值類型碼,但他並不影響類的類型。
方法:以一個新的類型替換該數值類型碼。
動機:接受類型碼作爲參數的函數,所期望的實際是一個數值,無法強制使用符號名,會大大降低代碼的可讀性,從而成爲bug之源。但是如果將那樣的數值替換成一個類,編譯器就可以進行校驗,只要未這個類提供工廠函數,就可以始終保證只有合法的實例纔會被創建,而且它們都會被傳遞給正確的宿主對象。
做法:1.爲類型碼建立一個類;2.修改源類實現,讓它使用上述新建的類;3.編譯測試;4.對於源類中每一個使用類型碼的函數,相應建立一個函數,讓新函數使用新建的類;5.逐一修改源類用戶,編譯測試;6.刪除使用類型碼的舊接口,並刪除保存舊類型碼的靜態變量;7.編譯測試。

8.14 Replace Type Code with Subclasses(以子類取代類型碼)
問題:你有一個不可變的類型碼,它會影響類的行爲。
方法:以子類取代這個類型碼。
動機:如果面對的類型碼不會影響宿主類的行爲,可以使用Replace Type Code with Class(218)來處理它們,但是如果類型碼會影響宿主類的行爲,那麼最好的辦法就是藉助多態來處理變化行爲。一般來說就像switch這樣的條件表達式,可能有switch或者if-then-else兩種結構,這種情況下,應該以Replace Conditional with Polymorphism(255)進行重構,但是爲了順利進行重構,首先應該將類型碼替換爲可擁有多態行爲的繼承體系,爲了建立這樣的繼承體系,最簡單的方法就是Replace Type Code with Subclasses(223)。
做法:1.使用Self Encapsulate Field(171)將類型碼自我封裝起來;2.爲類型碼的每一個數值建立一個相應的子類,在每個子類中覆寫類型碼的取值函數,使其返回相應的類型碼值;3.每建立一個新的子類,編譯測試;4.從超類中刪除保存類型碼的字段,將類型碼訪問函數聲明爲抽象函數;5.編譯測試。

8.15 Replace Type Code with State/Strategy(以State/Strategy取代類型碼)
問題:你有一個類型碼,它會影響類的行爲,但是你無法通過繼承手法消除它。
方法:以狀態對象取代類型碼。
動機:如果類型碼的值在對象生命週期中發生變化或者其他原因使得宿主類不能被繼承,你也可以使用本重構。
做法:1.使用Self Encapsulate Field(171)將類型碼自我封裝起來;2.新建一個類,根據類型碼的用途爲它命名,這就是一個狀態對象;3.爲這個新類添加子類,每個子類對應一種類型碼;4.在超類中建立一個抽象的查詢函數,用以返回類型碼,在每個子類中覆寫該函數,返回確切的類型碼;5.編譯;6.在源類中負責爲類型碼設值得函數,將查詢動作轉發給狀態對象;7.調整源類中衛類型碼設值的函數,將一個恰當的狀態對象子類賦值給“保存狀態對象”的那個字段;8.編譯測試。

8.16 Replace Subclass with Field(以字段取代子類)
問題:你的各個子類的唯一差別只在“返回常量數據”的函數身上。
方法:修改這些函數,使它們返回超累中的某個(新增)字段,然後銷燬子類。
動機:建立子類目的是爲了增加新特性或改變其行爲,有種變化行爲被稱爲“常量函數”,會返回一個硬編碼的值。儘管常量函數有用途,但是如果子類中只有常量函數,實在沒有足夠的存在價值,你可以在超類中涉及一個與常量函數返回值相應的字段,從而完全去除這樣的子類,如此就可以避免因集成而帶來的額外複雜性。
做法:1.對所有子類使用Replace Constructor with Factory Method(304);2.如果有任何代碼直接引用子類,令它改而引用超類;3.針對每個常量函數,在超類中聲明一個final字段;4.爲超類聲明一個protected構造函數,用以初始化這些新增字段;5.新建或修改子類構造函數,使它調用超累的新增構造函數;6.便宜額測試;7.在超類中實現所有常量函數,令它們返回相應字段值,然後將該函數從子類中刪掉;8.每刪除一個常量函數,編譯測試;9.子類中所有的常量函數都刪除後,使用Inline Method(117)將子類構造函數內聯到超類的工廠函數中;10.編譯測試;11.將子類刪除;12.編譯測試;13.重複“內聯構造函數,刪除子類的過程”,直到所有子類都被刪除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章