在撰寫 app 的時候,會有一些 manager 來處理商業邏輯。而這些 managers 本身會被其他的 class 或是 struct 的實體 (instance) 用到。這時候考量到 可測試 和 易抽換 ,就會使用「相依性注入」 (DI, Dependency Injection) 這個手法,把這個 manager 的實體,可以讓其他類別的實體使用。
會講到三個東西: Manageable, Manager 和 ManagerControllable
這篇用到寫法和命名皆採用 Swift 3 / iOS 10 SDK 的命名慣例,並以「Token 管理」作為輔助說明的範例。
管理者類型介面 - ~Manageable
針對介面,而非針對實作來撰寫程式碼
這句話應該很熟悉,原文是「program to an interface not an implementation」。
說到介面,在 Swift 就是 protocol ,protocol 可以讓我們拿來定義和整理預期行為的介面。
命名
在 Swift protocol 命名的慣例上,只要這個 protocol 的目的,是跟 可以 達成什麼事情有關係,字尾就會是 ~able
。
因為在這裡是期望實作這一類型 protocols 的 manager s 是要 可以 管理某個東西,所以在 manager 類型的 protocol 的命名,複合之後,就都會有 ~Manageable
這樣的字尾。
例如:
protocol TokenManageable {
/// 取用 token
var token: String? { get }
// ... 其他定義略
}
定義了一個 TokenManageable
這個 protocol ,並具有取用 token
的功能。
實作 - ~Manager
這個部分就很簡單,實作的 class 就會以 ~Manager
作為字尾。
Singleton (單例)
就像 iOS 原生的 NotificationCenter
一樣,我對 manager 類型的實作
- 使用 class
- 以 singleton 的形式存在
一方面也是有考量到測試的可 mock 性。
所以在實作的時候,通常會加上類似這樣的實作:
class TokenManager: TokenManageable {
// 主要的 type variable ,是個 singleton 物件
static let `default`: TokenManager = TokenManager()
// 如果期望這個 manager 不能有第二的實體,就必須把 init method 設定成 private
private init() {
}
/// MARK: - TokenManageable
var token: String? {
// 實務上可能由某處取出這個 token
return ""
}
// ... 其他實作略
}
可使用 - ~ManagerControllable
這個設計想解決的問題:
原本重構到使用 DI 的方式提供共用的 managers ,
但是這時候發現每個使用的 class/struct 都要實作的話,會有很多重複程式碼。
因此決定再封裝這一個部分,
同時給這個賦予 更容易識別 的名稱
===
當建立好 manager 的時候,就可以給其他實體使用,概念上是:
可控制某某 manager
先挑了很多英文單字當候選之後,
覺得 Control 比較符合我期望他的目的,
因此字尾最終以 ~ManagerControllable
的樣子呈現。
定義與實作
這一段有用到這些概念
- Dependency Injection (相依性注入)
- 為 Protocol 提供預設實作
先看這一段程式碼,
protocol TokenManagerControllable {
var tokenManager: TokenManageable { get }
}
extension TokenManagerControllable {
var tokenManager: TokenManageable {
return TokenManager.default
}
}
首先,TokenManagerControllable
定義可以取用 tokenManager
的 protocol 。
接著利用「 extension 可為 protocol 提供預設實作 」的特性,
讓這個 protocol 在不需要另外實作的情形之下,
只要某個類別使用 (conforms) 這個 protocol ,
他的實體只要呼叫 tokenManager
就可以取用到上一個段落時做出來的 singleton 物件 - TokenManager.default
。
實際使用
假設有某個 UIViewController 子類別 ViewController
想要使用 token manager 的話,只要像這樣 conforming TokenManagerControllable
,也不用再撰寫其他實作就可以使用了:
class ViewController: UIViewController, TokenManagerControllable {
override func viewDidLoad() {
super.viewDidLoad()
if let token = self.tokenManager.token {
print("Token 存在: \(token)")
} else {
print("Token 不存在")
}
}
// ... 其他實作略
}
結語
這一陣子在上班時間和下班的零碎時間,都花很多時間在思考這一類的東西
- Protocol oriented programming / 介面導向設計
- Clear and easy-understand behaviors / 清晰易懂的介面行為
- Testable / 可測試
- Embracing changing / 擁抱改變
- Easy maintainable / 易可維護
也趁著 Swift 3 改版和新專案的契機,把商業邏輯 manager 這個部分做得更完善;並把概念定下來,幫助在專案上能夠更迅速的實作和測試,語意上同時也更加精確表達他們想達成的目的。
在實務上,熟練後確實也幫助我更快更精確地加速撰寫單元測試案例、在商業需求上要變更需求的時候減少了非常多的替換成本。
這一段時間在商業邏輯層架構花的功非常多,下一個階段應該就是 UI 相關實作的調整了吧。雖然之前有做過 UI 模組化的嘗試,但是還是有不滿意的地方(例如難以測試),這就是下一個目標了!