代碼重構(三):數據重構規則

在《代碼重構(一):函數重構規則(Swift版)》和《代碼重構(二):類重構規則(Swift版)》中詳細的介紹了函數與類的重構規則。本篇博客延續之前博客的風格,分享一下在Swift語言中是如何對數據進行重構的。對數據重構是很有必要的,因爲我們的程序主要是對數據進行處理。如果你的業務邏輯非常複雜,那麼對數據進行合理的處理是很有必要的。對數據的組織形式以及操作進行重構,提高了代碼的可維護性以及可擴展性。

與函數重構與類重構類似,對數據結構的重構也是有一定的規則的。通過這些規則可以使你更好的組織數據,讓你的應用程序更爲健壯。在本篇博客中將會結合着Swift代碼實現的小實例來分析一下數據重構的規則,並討論一下何時使用那些重構規則進行數據重構。還是那句話“物極必反”呢,如果不恰當的使用重構規則,或者過度的使用重構規則不但起不到重構的作用,有時還會起到反作用。廢話少說,進入今天數據重構的主題。

一. Self Encapsulate Field (自封裝字段)

"自封裝字段"理解起來比較簡單,一句話概括:雖然字段對外是也隱藏的,但是還是有必要爲其添加getter方法,在類的內部使用getter方法來代替self.field,該方式稱爲自封裝字段,自己封裝的字段,自己使用。當然該重構規則不是必須執行的,因爲如果你直接使用self來訪問類的屬性如果不妨礙你做擴展或者維護,那麼也是可以的,畢竟直接訪問變量更爲容易閱讀。各有各的好處,間接訪問字段的好處是使你的程序更爲模塊化,可以更爲靈活的管理數據。比如在獲取值時,因爲後期需求的變化,該獲取的字段需要做一些計算,那麼間接訪問字段的方式就很容易解決這個問題,而直接訪問字段的方式就不是很好解決了。所以間接一下還是好處多多的,不過直接訪問不影響你的應用程序的話,也是無傷大雅的。

下方會通過一個實例來看一下間接訪問字段的好處。下方的IntRange類中的字段就沒有提供間接訪問的方法,在代碼中通過直接訪問的形式來使用的字段。這種做法對當前的程序影響不大,但是如果提出需求了。要在high賦值後,在IntRange對類進行一個較爲複雜的修改。那麼對於下方代碼而言,有兩種解決方案,就是在構函數中進行修改,在一個就是在使用self.high的地方進行修正,當然這兩種方法都不理想。最理性的方案是在相應字段的getter方法修改。

   

下方截圖就是爲InRange類中相應的字段自封裝了getter和setter方法,並在使用self.字段的地方使用該自封裝的方法代替(構造函數中對字段的初始化除外,因爲設置方法一般在對象創建完畢以後在調用,所以不能在創建對象時調用,當然Swift語言也不允許你在構造函數函數中調用設置方法)。下方紅框中的是我們添加的自封裝方法,綠框中是對自封裝方法的使用,白框中是需要注意的一點,構造函數中不能使用該設置函數。

     

當然,只添加上上述自封裝字段後,優點不明顯。當然子類CappedRange繼承了IntRange函數後,這種優點就被顯示了出來。在子類中CappedRange的high需要與新添加的字段cap進行比較,取較大的值作爲區間的上限。在這種情況下自封裝字段的優點就被凸顯了出來。在子類中只需要對getHigh()函數進行重新,在重寫的方法中進行相應的計算即可。因爲當在子類中調用inclued()方法時,在include()方法中調用的是子類的getHigh()方法。具體請看下方子類截圖:

    

 

二. Replace data Value with Object(以對象取代數據值)

 “以對象取代數據值”說白了就是我們常說的實體類,也就是Model類。Model的職責就將一些相關聯的數據組織在一起來表示一個實體。Model類比較簡單,一般只用於數據的存儲,其中有一些相關聯的字段,併爲這些相關聯的字段添加getter/和setter方法。下方是一個Person的數據模型,我們命名爲PersonModel,其中有三個表示Person屬性的字段name、birthday、sender。然後提供了一個構造器以及各個屬性對應的getter和setter方法。具體請看下方代碼所示:

   

 

三、Change Value to Reference (將值對象改變成引用對象)

在介紹“將值對象改變成引用對象”之前,我們先去了解一下值對象和引用對象的區別。先說一下值對象,比如兩個相等的數值,存入了兩個值對象中,這兩個值對象在內存中分別佔有兩塊不同的區域,所以改變其中一個值不會引起另一個值得變化。而引用對象正好相反,一個內存區域被多個引用指針所引用,這些引用指針即爲引用對象,因爲多個指針指向同一塊內存地址,所以無論你改變哪一個指針中的值,其他引用對象的值也會跟着變化。

基於值對象和引用對象的特點,我們有時候根據程序的上下文和需求需要將一些值類型改變成引用類型。因爲有時候需要一些類的一些對象在應用程序中唯一。這和單例模式又有一些區別,單例就是一個類只能生成一個對象,而“將值對象改變成引用對象”面臨的就是類可以創建多個對象,但是這多個對象在程序中是唯一的,並且在某一個引用點修改對象中的屬性時,其他引用點的對象值也會隨之改變。下方就通過一個訂單和用戶的關係來觀察一下這個規則。

1. 值引用的實例

(1) 首先我們需要創建一個消費者也就是Customer類。Customer類比較簡單,其實就是一個數據實體類。其中有name和idCard屬性並對應着getter/setter方法,具體代碼如下所示:

   

(2)、緊接着我們需要創建一個訂單類,在訂單創建時我們需要爲該訂單關聯一個Customer(當然這爲了簡化實例,我們省略了Order中的其他字段)。該Order類的代碼也是比較簡單的在此就不做過的的贅述了。不過有一點需要注意的是爲了測試,我們將customer設計成值類型,也就是每個Order中的customer都會佔用不同的內存空間,這也就是值類型的特點之一。

  

 (3).創建完Order與Customer類後,緊接着我們要創建測試用例了。並通過測試用例來發現問題,並在重構時對該問題進行解決。在測試用例中我們創建了三個訂單,爲每個訂單關聯一個Customer。從測試用例中可以看出,關聯的消費者數據爲同一個人,但是這一個人在內存中佔用了不同的存儲空間,如果一個訂單中的用戶信息進行了更改,那麼其他訂單中的用戶信息是不會更新的。如果創建完用戶後,信息不可更改,雖然浪費點存儲空間,但是使用值類型是沒用問題的。一旦某個訂單修改了用戶名稱,那麼就會出現數據不同步的問題。

   

2.將Order中Customer改爲引用類型重新設計Order類

因爲在Swift語言中類本身就是引用類型,所以在設計Order時,我們值需要將其中的customer字段改成引用外部的Customer類的對象即可。這樣一來多個訂單可以引用同一個用戶了,而且一個訂單對用戶信息修改後,其他訂單的用戶信息也會隨之改變。要實現這一點需要對Order的構造函數和customer的設置函數進行修改,將在Order內部創建Customer對象的方式改變成將外部Customer對象的引用賦值給Order中的custom對象。說白了,修改後的Order中的customer對象就是外部對象的一個引用。這種方法可以將值對象改變成引用對象

   

上面這種做法可以將值對象改變成引用對象,但是代價就是改變Order創建的方式。上面代碼修改完了,那麼我們的測試用例也就作廢了,因爲Order的創建方式進行了修改,需要外部傳入一個Customer對象,下方截圖就是我們修改後的測試用例。(如果你是在你的工程中這麼去將值對象修改引用對象的,不建議這麼做,下面會給出比較好的解決方案)。

   

3.從根本上進行重構

上面代碼的修改不能稱爲代碼的重構,因爲其改變的是不僅僅是模塊內部的結構,而且修改了模塊的調用方式。也就是說裏外都被修改了,這與我們重構所提倡的“改變模塊內部結構,而不改變對外調用方式”所相悖。所以在代碼重構時不要這麼做了,因爲上面的這種做法的成本會很高,並且出現BUG的機率也會提高。因爲每個使用訂單的地方都會創建一個Customer的類來支持訂單的創建,那麼問題來了,如果同一用戶在不同地方創建訂單怎麼辦?所以上面的做法還是有問題的,終歸是治標不治本。所以我們要從根本上來解決這個問題。因爲該問題是因爲Customer數據不同步引起的,所以我們還得從Customer來下手。

該部分的重構,在第一部分的基礎上做起。我們本次重構的目標就是“不改變對外調用方式,但能保持每個用戶是唯一的”。好接下來就開始我們真正的重構工作。在本次重構中,依照重構的規則,我們不會去修改我們的測試用例,這一點很重要。

(1)從根本解決問題,首先我們對Customer進行重構。在Customer中添加了一個靜態的私有變量customers, 該靜態私有變量是字典類型。其中存儲的就是每次創建的消費者信息。在字典中每個消費者的key爲消費者獨一無二的身份證信息(idCard)。在添加完上述變量後,我們需要爲創建一個工廠方法createCustomer() 在工廠方法中,如果當前傳入的用戶信息未被存入到字典中,我們就對其進行創建存入字典,並返回該用戶信息。如果傳入的用戶已經被創建過,那麼就從字典中直接取出用戶對象並返回。具體做法如下所示。

    

(2)、對Customer類修改完畢後,我們需要在Order中通過Customer的工廠方法來獲取Customer類的實例,這樣就能保證Order中的customer對象也是引用對象了。不過此時的引用對象是從Customer中獲取的,而不是外部傳過來的。下方是Order類中對工廠方法的調用,這樣做的好處就是,我們只對模塊的內部進行了修改,而測試用例無需修改。

   

 (3)、對此次重進行測試,我們任然使用第一部分使用的測試用例。也就是說該模塊對外的接口是沒有變化的,下方就是對重構後的代碼的測試結果。由結果可以看出,在不同訂單中的用戶,只要是信息一致,那麼其內存地址是一致的。也就是經過重構,我們將原來的值對象改成了引用對象。

    

 

四、Change Reference to Value(將引用對象改爲值對象)

將引用對象改爲值對象,該重構規則正好與上面相反。在一些情況下使用值對象更爲簡單,更易管理,但前提是該值對象很小並且不會被改變。在這種情況下你就沒有必要使用引用對象了。從上面的示例來看,使用引用對象實現起來還是較爲複雜的。還是那句話,如果你的對象非常小,而且在創建後其中的數據不會被改變,如果需要改變就必須在創建一個新的對象來替換原來的對象。在這種情況下使用值對象是完全可以的。在此就不做過多的贅述了。

不過在使用值對象時,你最好爲值對象提供一個重載的相等運算符用來比較值對象中的值。也就是說只要是值對象中的每個屬性的值都相同,那麼這兩個值對象就相等。至於如何對“==” 運算符進行重載就不做過多的贅述了,因爲該知識點不是本篇博客的重點。

 

五、Replace Array or Dictionary with Object(以對象取代數組或字典)

這一點呢和本篇博客的第二部分其實是一個。就是當你使用數組或者字典來組織數據,這些數據組合起來代表一定的意義,這是最好將其定義成一個實體類。還是那句話,定義成實體類後,數據更易管理, 便於後期需求的迭代。下方代碼段就是講相應的字典和數組封裝成一個實體類,因爲確實比較簡單,在此就不做過多的贅述了。具體請參加下方代碼段。

    

 

六、Duplicate Observed Data(複製被監測數據”)

這一部分是比較重要的部分,也是在做UI開發時經常遇到的部分。用大白話將就是你的業務邏輯與GUI柔和在了一起,因爲UI作爲數據的入口,所以在寫程序時,我們就很容易將數據處理的方式與UI寫在一起。這樣做是非常不好的,不利於代碼的維護,也不利於代碼的可讀性。隨着需求不斷的迭代,版本不斷的更新,UI與業務邏輯融合的代碼會變得非常難於維護。所以我們還是有必要將於UI無關的代碼從UI中進行分離,關於如何進行分層宏觀的做法請參加之前發佈的博客《iOS開發之淺談MVVM的架構設計與團隊協作》。

今天博客中的該部分是分層的微觀的東西,也就是具體如何將業務邏輯從GUI中進行剝離。所以在接下來的實例中是和UI實現有關的,會根據一個比較簡單的Demo來一步步的將UI中的業務邏輯進行分離。進入該部分的主題。複製被監測數據”簡單的說,就是將UI提供的數據複製一份到我們的業務邏輯層,然後與UI相應的數據進行關聯,UI數據變化,被複制的業務邏輯中的數據也會隨之變化。這一點也就是所謂的"響應式編程"吧,關於響應式編程,iOS開發中會經常用到ReactiveCocoa這個框架,關於ReactiveCocoa的內容,請參見之前的博客《iOS開發之ReactiveCocoa下的MVVM》。今天的示例中,使用了一個比較簡單的方式來同步這些數據,使用了"事件監聽機制"。下方就創建一個比較簡單的Demo。

1.創建示例

要創建的示例比較簡單,在UI方面,只有三個輸入框用來接收加數與被加數,以及用來顯示兩數之和。然後使用兩個UILabel來顯示+號與=號。我們要實現的功能就是改變其中一個加數與被加數時,自動計算兩個數的和並顯示。

 要實現上述功能的代碼也是比較簡單的,總共沒有幾行,下方這個類就是實現該功能的全部代碼。代碼的核心功能就是“獲取加數與被加數的和,然後在加數與被加數的值有一個改變時,就會計算兩者之和,並將和賦值給最後一個輸入框進行顯示”。具體代碼如下所示。

複製代碼
 1 class AddViewControllerBack: UIViewController {
 2     
 3     //三個輸入框對應的字段
 4     @IBOutlet var firstNumberTextField: UITextField!
 5     @IBOutlet var secondNumberTextField: UITextField!
 6     @IBOutlet var resultTextField: UITextField!
 7     
 8     override func viewDidLoad() {
 9         super.viewDidLoad()
10     }
11     
12     //獲取第一個輸入框的值
13     func getFirstNumber() -> String {
14         return firstNumberTextField.text!
15     }
16     
17     //獲取第二個輸入框的值
18     func getSecondNumber() -> String {
19         return secondNumberTextField.text!
20     }
21     
22     //加數與被加數中的值改變時會調用的方法
23     @IBAction func textFieldChange(sender: AnyObject) {
24         self.resultTextField.text = calculate(getFirstNumber(), second: getSecondNumber())
25     }
26     
27     
28     //計算兩個數的值
29     func calculate(first: String, second: String) -> String {
30         return String(stringToInt(first) + stringToInt(second))
31     }
32     
33     //將字符串安全的轉變成整數的函數
34     func stringToInt(str: String) -> Int {
35         guard let result = Int(str) else {
36             return 0
37         }
38         return result
39     }
40 }
複製代碼

 

2.對上述代碼進行分析並重構

因爲代碼比較簡單,所以很容易進行分析。在上述UI代碼中,我們很清楚的看到後兩個函數,也就是calculate()與stringToInt()函數是數據處理的部分,只依賴於數據,與UI關係不是很大,所以我們可以使用複製被監測數據規則將該段業務邏輯代碼進行提取重構。重構後UI以及UI對外的工作方式不變。

下方的Calculate類就是我們提取的數據業務類,負責處理數據。在該類中我們創建了三個屬性來與UI中的輸入框進行對應,這也就是所說的複製“被監測的數據”。因爲和也就是resultNumber是由firstNumber和SecondNumber計算而來的,所以我們就把resultNumber定義成了計算屬性,而firstNumber和secondNumber爲存儲屬性。併爲存儲屬性提供setter方法。在Calculate類的構造函數中,我們爲兩個值指定了初始化數據也就是“0”。最下方的那兩個函數就是我們從UI中直接拷貝過來的數據,一點沒有修改,也是可以工作的,因爲這部分代碼只依賴於數據,而不依賴於UI。

    

創建爲相應的業務邏輯處理類並提取完業務邏輯後,我們需要將業務邏輯中的數據,也就是複製過來的數據與UI中的數據提供者進行綁定,並返回計算結果。下方紅框中就是我們要修改的部分,在UI中我們刪除掉處理業務數據的代碼,然後創建也給Calculate對象,並在相應的事件監聽的方法中更新Calculate對象中的數據。如下所示

   

 

七、Change Unidirectional Association to Bidirectional(將單向關聯改爲雙向關聯)

要介紹本部分呢,我想引用本篇博文中第(三)部分是實例。因爲在第三部分的實例中Customer與Order的關係是單向關聯的,也就是說Order引用了Customer, 而Customer沒有引用Order。換句話說,我們知道這個訂單是誰的,但你不知道只通過用戶你是無法知道他有多少訂單的。爲了只通過用戶我們就能知道該用戶有多少訂單,那麼我們需要使用到“將單向關聯改爲雙向關聯”這條規則。

1. 在Customer類中添加上指向Order類的鏈

因爲Customer沒有指向Order類的鏈,所以我們不能獲取到該用戶有多少訂單,現在我們就要添加上這條鏈。將單向關聯改爲雙向關聯,具體做法是在Customer中添加一個數組,該數組中存儲的就是該用戶所擁有的訂單。這個數組就是我們添加的鏈。數組如下:

1     //添加與Order關聯的鏈,一個用戶有多個訂單
2     private var orders:Array<Order> = []

在Customer中值只添加數組也是不行的呢,根據之前提到的重構規則,我們要爲數組封裝相應的操作方法的,下方就是我們要在Customer中添加的操作數組的方法。具體代碼如下所示:

複製代碼
1     //====================添加==================
2     func addOrder(order: Order) {
3         self.orders.append(order)
4     }
5     
6     func getOrders() -> Array<Order> {
7         return self.orders
8     }
複製代碼

在Order類關聯Customer時,建立Customer到Order的關聯。也就是將當前訂單添加進該用戶對應的訂單數組中,具體做法如下:

     

與之對應的規則是Change Bidirectional Association to Unidirectional(將雙向關聯改爲單向關聯),就是根據特定需求刪去一個鏈。就是說,原來需要雙向鏈,可如今由於需求變更單向關聯即可,那麼你就應該將雙向關聯改爲單向關聯。

 

八、Replace Magic Number with Synbolic Constant(以字面常量取代魔法數)

這一點說白了就是不要在你的應用程序中直接出現字數值。這一點很好理解,在使用字面數值時,我們要使用定義好的常量來定義。因爲這樣更易於維護,如果同一個字面數值寫的到處都是,維護起來及其困難。當使用字面常量時維護起來就容易許多。該規則比較容易理解,在此不做過多的贅述。看下方實例即可。對於下方的實例而言,如果在版本迭代中所需的PI的精度有所改變,那麼對於替換後的程序而言,我們只需修改這個常量的值即可。

複製代碼
1 func test(height: Double) -> Double {
2     return 3.141592654 * height
3 }
4 
5 //替換
6 let PI = 3.141592654
7 func test1(height: Double) -> Double {
8     return PI * height
9 }
複製代碼

 

九、Encapsulate Field(封裝字段)

當你的類中有對外開放字段時,最好將其進行封裝,不要直接使用對象來訪問該字段,該優缺點與上述的“自封裝字段”的優缺點類似。因爲直接訪問類的字段,會降低程序的模塊化,不利於程序的擴充和功能的添加。再者封裝是面向對象的特徵之一,所以我們需要將字段變成私有的,然後對外提供相應的setter和getter方法。具體做法如下所示。

複製代碼
 1 //重構前
 2 class Person {
 3     var name: String = ""
 4     
 5     init(name: String) {
 6         self.name = name
 7     }
 8 }
 9 
10 //重構後
11 class Person {
12     private var name: String = ""
13     
14     init(name: String) {
15         self.name = name
16     }
17     
18     func getName() -> String {
19         return name
20     }
21     
22     func setName(name: String) {
23         self.name = "China:" + name
24     }
25 }
複製代碼

 

十、Encapsulate Collection(封裝集合)

“封裝集合”這一重構規則應該來說並不難理解。當你的類中有集合時,爲了對該集合進行封裝,你需要爲集合創建相應的操作方法,例如增刪改查等等。下方就通過一個不封裝集合的實例,看一下缺點。然後將其重構。關於“封裝集合”具體的細節參見下方實例。

1.未封裝集合的實例

下方我們先創建一個圖書館圖書類,爲了簡化示例,該圖書類只有一個書名。下方代碼段就是這個圖書類,如下所示:

複製代碼
class LibraryBook {
    private var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func getName() -> String {
        return self.name
    }
}
複製代碼

 

緊接着要創建一個借書者,借書者中有兩個字段,一個是借書者的名字,另一個是所借書籍的數組。在Lender中我們沒有爲lendBooks數組封裝相應的方法,只爲其提供了getter/setter方法,具體代碼如下所示。

複製代碼
 1 class Lender {
 2     private var name: String
 3     private var lendBooks: Array<LibraryBook> = []
 4     
 5     init(name: String) {
 6         self.name = name
 7     }
 8     
 9     func getName() -> String {
10         return self.name
11     }
12     
13     func setLendBooks(books: Array<LibraryBook>) {
14         self.lendBooks = books
15     }
16     
17     func getLendBooks() -> Array<LibraryBook> {
18         return self.lendBooks
19     }
20 }
複製代碼

 

緊接着我們要創建一個測試用例,觀察這兩個類的使用方式。由下面程序的註釋可知,首先我們需要創建一個books的數組,該數組就像一個籃子似的,它可以存儲我們要借的書籍。讓後將創建的書籍添加到該數組中,最後將books賦值給借書人中的lendBooks。如果要對書籍進行修改,那麼只有先獲取借書人的lendBooks, 然後進行修改,最後再將修改後的值賦值回去。

複製代碼
 1 //先創建一個書籍數組
 2 var books: Array<LibraryBook> = []
 3 //添加要借的書籍
 4 books.append(LibraryBook(name: "《雪碧加鹽》"))
 5 books.append(LibraryBook(name: "《格林童話》"))
 6 books.append(LibraryBook(name: "《智慧意林》"))
 7 
 8 //創建借書人
 9 let lender: Lender = Lender(name: "ZeluLi")
10 lender.setLendBooks(books)
11 
12 //獲取所借書籍
13 var myBooks = lender.getLendBooks()
14 
15 //對書籍數組修改後再賦值回去
16 myBooks.removeFirst()
17 lender.setLendBooks(myBooks)
複製代碼

 

2.爲上面的Lender類添加相應的集合操作的方法

由上面的測試用例可以看出,Lender類封裝的不好。因爲其使用方式以及調用流程太麻煩,所以我們得重新對其進行封裝。所以就會用到“Encapsulate Collection”原則。下面我們就會爲Lender添加上相應的集合操作的方法。說白了,就是講上面測試用例做的一部分工作放到Lender類中。下方是爲Lender添加的對lendBooks相應的操作方法。下方代碼中的Lender類與上面的Lender類中的lendBooks不同,我們使用了另一個集合類型,也就是字典,而字典的key就是書名,字典的值就是書的對象。具體代碼如下所示:

   

 

經過上面這樣一封裝的話,使用起來就更爲合理與順手了。用大白話講,就是好用。下方是我們重新封裝後的測試用例,簡單了不少,而且組織也更爲合理。具體請看下方代碼段:

    

 

十一、Replace Subclass with Fields(以字段取代子類)

什麼叫“以字段取代子類”呢?就是當你的各個子類中唯一的差別只在“返回常量數據”的函數上。當遇到這種情況時,你就可以將這個返回的數據放到父類中,並在父類中創建相應的工廠方法,然後將子類刪除即可。直接這樣說也許有些抽象,接下來,我們會通過一個小的Demo來看一下這個規則具體如何應用。1.創建多個子類,並每個子類只有一個函數的返回值不同

接下來我們就要創建重構前的代碼了。首先我們創建一個PersonType協議(也就是一個抽象類),該協議有兩個方法,一個是isMale(),如果是子類是男性就返回true,如果子類是女性就返回false。還有一個是getCode()函數,如果子類是男性就返回“M”,如果是子類是女性就返回“F”。 這兩個子類的差別就在於各個函數返回的值不同。下方是PersonType的具體代碼。

1 protocol PersonType {
2     func isMale() -> Bool
3     func getCode() -> String
4 }

然後我們基於PersonType創建兩個子類,一個是Male表示男性,一個是Female表示女性。具體代碼如下:

複製代碼
 1 class Male: PersonType {
 2     func isMale() -> Bool {
 3         return true
 4     }
 5     
 6     func getCode() -> String {
 7         return SenderCode.Male.rawValue
 8     }
 9 }
10 
11 class Female: PersonType {
12     func isMale() -> Bool {
13         return false
14     }
15     
16     func getCode() -> String {
17         return SenderCode.Female.rawValue
18     }
19 }
複製代碼

上述代碼的SenderCode是我們自定義的枚舉類型,用來表示"M"與“F”,枚舉的代碼如下:

1 enum SenderCode: String {
2     case Male = "M"
3     case Female = "F"
4 }

 

2.以字段取代子類

從上面的代碼容易看出,Male與Female類實現相同的接口,但接口函數在兩個類中的返回值是不同的。這時候我們就可以使用“以字段取代子類”的方式來進行重構,下方截圖就是重構後的代碼片段。

下方代碼中,將PersonType聲明瞭一個類,在類中添加了兩個字段,一個是isMale,另一個是code,這兩個字段恰好是上述兩個子類函數中返回的不同值。這也就是使用字段來取代子類,因爲有了這兩個字段,我們就可以不用去創建子類了,而是直接在PersonType中通過工廠方法根據不同的性別分別給這兩個新加的字段賦上不同的值。具體做法如下。

    

 

經過上面這段代碼重構後,我們就可以調用PersonType的不同的工廠方法來創建不同的性別了。測試用例如下所示:

    

 

OK~今天博客的內容也夠多的了,那就先到這兒。關於重構的其他規則,還會在後期的博客中繼續更新。

今天博客中是示例在GitHub上的分享地址爲:https://github.com/lizelu/CodeRefactoring-Swift

 

作者:青玉伏案
出處:http://www.cnblogs.com/ludashi/
本文版權歸作者和共博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
如果文中有什麼錯誤,歡迎指出。以免更多的人被誤導。 

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