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

来源:http://www.cnblogs.com/ludashi/archive/2016/03/14/5236024.html
-Advertisement-
Play Games

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


在《代碼重構(一):函數重構規則(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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  •  
  • 一個基於redis的處理session的方法,如下。 補充: php.ini文件中的session.gc_probability與session.gc_divisor兩個配置選項共同決定gc函數調用的時機。預設值分為為1和1000,表示每個請求只有1/1000的機會調用gc函數。
  •     問題的產生,必有其理由。說白點也就是客戶需要,沒辦法的事。不過也到給我們添了不少麻煩。本人也希望大牛們能給在下提提更多的思路,在下在此謝過。     具體是這樣:   1.要記錄操作人員,操作時間,操作相應模塊          2.要記錄操作的原始數據(ps:列級別)和變更後數據    
  • 前言:OpenCV對圖像及視頻的處理方便且很專業,對於攝像頭的支持也很好,但有個不足就是它雖然具有GUI模塊(即highgui),但是實在是很簡陋,就連一個按鍵都無法直接實現(需要藉助滾動條實現),這一點難以滿足可視化的圖像處理的想法;另一方面,Qt作為一個優秀的圖形庫,在GUI上表現出色,且界面設
  •                                                   
  • 瀏覽器和伺服器之間是通過 HTTP 協議進行連接通訊的。這是一種基於請求和響應模型的協議。瀏覽器通過 URL 向伺服器發起請求,Web 伺服器接收到請求,執行一段程式,然後做出響應,發送相應的html代碼給客戶端。 這就有了一個問題,Web 伺服器執行一段程式,可能幾毫秒就完成,也可能幾分鐘都完不成
  • 1.&按位“與”的計算是把兩個數字分別寫成二進位形式,然後按照每一位進行比較,&計算中,只要有一個是0就算成02.|運算轉換成2進位進行比較,兩個位只要有一個為1,那麼結果就是1,否則就為03.^兩個數轉換為2進位然後比較位,相同則結果為0,不同則結果為1
  • Maven引入    我們都知道,在JDK1.5之前,Java中要進行業務併發時,通常需要有程式員獨立完成代碼實現,當然也有一些開源的框架提供了這些功能,但是這些依然沒有JDK自帶的功能使用起來方便。而當針對高質量Java多線程併發程式設計時,為防止死蹦等現象的出現,比如使用java之前的wait(
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...