案例:
假設現在有一個工具類,目的是把傳入頁面指定區域渲染成紅色
不好的設計
定義一個基類 BaseFlushedViewController: UIViewController
,返回一個 flushArea
, 供工具類進行染色。
class FlushHelper {
static func flush(_ viewController: BaseFlushedViewController) {
viewController.flushArea().backgroundColor = .red
}
}
class BaseFlushedViewController: UIViewController {
func flushArea() -> UIView {
return view
}
}
複製程式碼
那麼我們傳入的引數必須是他的子類,而且由於每個頁面可能需要重新整理的檢視是不同的,我們理論上應該重寫他的 flushArea
方法
這樣做的問題有兩個:
- 新建的頁面類可能會忘記重寫該方法
- 如果需要傳入的頁面是一個
UINavigatiionController
或UITabbarController
呢?(有可能我現在要渲染導航欄或底部標籤欄),那麼我還要再在工具類中新增兩個介面適應。這顯然不是我們想要的
class FlushHelper {
static func flush(_ viewController: BaseFlushedViewController) {
viewController.flushArea().backgroundColor = .red
}
static func flush(_ viewController: BaseFlushedNavViewController) {
viewController.flushArea().backgroundColor = .red
}
static func flush(_ viewController: BaseFlushedTabViewController) {
viewController.flushArea().backgroundColor = .red
}
}
class BaseFlushedViewController: UIViewController {
func flushArea() -> UIView {
return view
}
}
class BaseFlushedNavViewController: UINavigationController {
func flushArea() -> UIView {
return view
}
}
class BaseFlushedTabViewController: UITabBarController {
func flushArea() -> UIView {
return tabBar
}
}
複製程式碼
面相介面的設計
定義一個協議
protocol Flushable {
func flushArea() -> UIView
}
class FlushHelper {
static func flush(_ viewController: UIViewController & Flushable) {
viewController.flushArea().backgroundColor = .red
}
}
class SomeViewController: UIViewController, Flushable {
func flushArea() -> UIView {
return view
}
}
class SomeNavViewController: UINavigationController, Flushable {
func flushArea() -> UIView {
return navigationBar
}
}
class SomeFlushedTabViewController: UITabBarController, Flushable {
func flushArea() -> UIView {
return tabBar
}
}
複製程式碼
將工具類的介面統一成 UIViewController & Flushable
這樣做的好處:
- 呼叫工具類介面時,十分明確的知道傳入的頁面要去實現
flushArea
方法,不存上文提到的在繼承之後忘記重寫的情況 - 適配所有的
UIViewController
及其子類。不需要工具類再開放額外的介面
比較
看起來面向介面的方法和使用基類的方法相比,只是工具類中的介面統一成了一個。但實際上,面向介面的方法中,SomeNavViewController
和 SomeFlushedTabViewController
都是 UIViewController
的一種特例。而使用基類的實現方法中,三種 Base
類則是平級的基類,是為了覆蓋所有的頁面型別。
假設如果有一種新的導航頁面,那麼面向基類的方法,就還要去新增一個新的基類覆蓋這個特例,面向介面的則只需要擴充套件出一個新的型別即可,工具類介面不需要新增。
以上的思想,就是物件導向設計規則中 開閉原則的一種體現(若有錯誤歡迎指正)。