善用 Static Factory Method 重構程式碼 讓它更流暢好讀!


在使用一個物件之前,我們經常會需要對其進行設定。比如說,使用一個 UIView 之前,有時我們會需要指定它的背景色彩等屬性:

這些設定程式碼跟其它的邏輯程式碼是相當不同的存在。它們往往只跟被設定的那個物件有關,像這裡就是只跟 view 有關,跟 ViewController 無關。所以,要重構它們也是相對上簡單的。比如說,我們可以直接把它們包裝成一個 ⋯⋯

自由函數工廠方法

我們把工廠方法寫成一個 private 的自由函數,因為這樣可以防止它去存取 ViewController 的任何屬性或方法。而在 ViewController 裡,我們也只需要一行程式碼就可以取用已經設定好的 UIView 物件,使程式碼更易讀。

然而自由函數如果一多,管理起來就會相對困難。首先,是要記得函數名才能呼叫,這還可以靠在命名時以 make- 做工廠方法開頭的慣例來解決。再來,是不同回傳型別的函數,都會在 Xcode 的自動完成列表裡面出現。所以,如果我們有另一個這樣的工廠方法:

那當我們輸入 make 的時候,即使是要產生拿來指派給 self.viewUIView 物件,makeView()makeLoadingViewController 都會同時出現。

free-function-factory-method

所以,雖然自由函數工廠方法是很好的重構與解耦工具,但使用上總是有那麼一點的不方便。還好 Swift 提供了另一種工具,既具備自由函數的解耦性,又能充分運用 Xcode 的自動完成系統,那就是 ⋯⋯

Static Factory Method!

在 Swift 裡,只要在某個方法前面加上 static 關鍵字,就會使它成為是屬於那個型別本身的方法。如果我們把型別當成是製造物件的藍圖的話,那 static method 就等於是藍圖本身的方法。而由於藍圖本身最大的用途就是拿來製造物件,所以它的方法也就常常都是工廠方法,用來補全 initializer 所不能做到的事。

甚麼是 initializer 做不到的呢?

  1. 命名:Initializer 只能對參數命名,沒辦法對方法本身命名。所以,同一個型別裡沒辦法有兩個不同的沒有參數的 initializer。
  2. 多型:Initializer 所產生的必然是該型別的物件,不會是它的子類型。比如說我們沒辦法使一個 UIView 的 initializer 回傳一個 UIButton

延續前面的例子,我們可以把 makeView() 改寫成 UIView 的 static factory method:

如此一來,當我們輸入到 self.view = . 的時候,Xcode 就可以判斷型別,並在自動完成列表裡,列出該型別所有的 initializer,以及正確回傳型別的 static method:

Static-Factory-Method-1

這樣就可以過濾掉所有不相關的函數與方法了。

實戰重構 UIAlertController

UIAlertController 是最容易產生 boilerplate code 的物件之一。即使是一個普通的確認警告,都得花上好幾行程式碼來實作:

幸好,我們有 static factory method 可以用。先把標題、UIAlertAction 等設定程式碼丟到一個 static method 裡,並設一個 handler 屬性來定義按下確認後要執行的動作:

然後,我們就可以在 ViewController 裡用它來產生 alert controller:

將原本繁複的程式碼縮減成這樣已經很棒了,但我們並沒有發揮到 Xcode 的自動完成功能。要怎麼做才有效呢?很簡單,把 static factory method 改定義在 UIViewController 底下就好了:

這樣,我們就可以在打出 present(. 之後,直接在自動完成列表裡選取 confirmationAlert(handler:) 來使用了。

Static-Factory-Method-2

這是因為 Xcode 的自動完成功能,會從參數的型別(UIViewController)找 initializer(init()init(nibName:bundle:)),與可回傳同型別物件的 static method(confirmationAlert(title:message:handler:) 回傳的 UIAlertControllerUIViewController 的子類型)。如果 static method 的回傳類型不能被當作型別本身來用的話(比如說在 UIViewController 裡回傳 Int),那它就不會出現在自動完成列表裡面。

而最後在 ViewController 裡面,就會只剩這樣的程式碼:

是不是更簡單好讀了呢?而這只需要我們多寫一個 static factory method,在 UIViewController 的 extension 裡面。

結論

其實所謂的重構,追根究底來說也就是把程式碼放到對的地方而已。用程式設計的術語來說的話,就是把它們放到對的 domain(領域),或者說 namespace(命名空間)。Static method 相對於自由函數來說,差別就在於它被放到了型別裡面,而型別本身就是一個 domain、一個 namespace。前面提到的設定用程式碼,多半是屬於被設定的物件的 domain,所以很適合放到該物件的型別裡面去。這樣子整理程式碼除了使人類讀起來更清楚之外,Xcode 也能夠依它來提供更完善的自動完成列表。搭配 Swift 的型別系統,更可以在很多場合省略掉型別的名字,使程式碼更流暢好讀。

某種方面來說,static factory method 跟 initializer 很類似,都是用來產生物件的方法,也都掛在某個型別底下。但是,static factory method 具有更強大的多型能力,也可以命名,比起 initializer 來說更為彈性。如果你希望用到 Xcode 的自動完成系統,但 initializer 又無法達成你的需求時,static factory method 可能是一個好選擇呢!


iOS 開發者、寫作者、filmmaker。現正負責開發 Storyboards by narrativesaw 此一故事板文件 app 中。深深認同 Swift 對於程式碼易讀性的重視。個人網站:lihenghsu.com。電郵:[email protected]

blog comments powered by Disqus
Shares
Share This