Swift 封裝篇
來自專欄 極光日報
簡評:在日益壯大的項目工程中,保持代碼的封裝性是一個很大的挑戰。隨著新功能的添加,對象通常會承擔新的職責,需要也其他的對象一起工作,有時新的改動可能會違背最初的設計,避免抽象泄露(leaking abstractions)不是一件容易的事情。
好的 API 規範可以幫助我們封裝代碼避免與其他類型共享不必要的實現細節。下面幾種方法是實現代碼封裝的常見方法。
隱藏實現細節
隱藏實現細節可以幫助我們減少不必要的錯誤,例如:我們構建了一個 ProfileViewController 類用於顯示當前登錄用戶的配置文件。它具有一個標題視圖(headerView),並且我們在 viewDidLoad 中設置 delegate,代碼如下所示:
class ProfileViewController: UIViewController, ProfileHeaderViewDelegate { lazy var headerView = ProfileHeaderView() override func viewDidLoad() { super.viewDidLoad() headerView.delegate = self view.addSubview(headerView) }}
這樣子咋一看沒有問題。但是假如你要將這個類給同事用,同事需要實現這樣一種功能:可讓用戶通過應用內購買來解鎖高級模式,這種模式下 headerView 需要變得更加酷炫。由於 headerView 沒有添加 private/fileprivate 修飾,所以這個 headerView 相當於暴露給了你的同事,他很可能用如下代碼實現這個 vip 功能:
func userDidUnlockPremiumSubscription() { profileViewController.headerView = PremiumHeaderView()}
當執行上面函數的時候原來的 headerView 已經丟失,並且新的 headerView 也沒有設置 delegate,這樣可能應用會卡在 ProfileViewController 這個界面,最後測試會直接找到你的頭上。
所以直接在介面上現在這種類情況,可以避免不必要的麻煩,這就是封裝的意義。
我們將上面代碼改動一下:
class ProfileViewController: UIViewController, ProfileHeaderViewDelegate { private lazy var headerView = ProfileHeaderView()}
這個時候你的同事找上門了,說內購成功後需要顯示 vip 的專屬界面。ok 我們只需要擴展原來的功能暴露一個 enterMode 方法即可:
extension ProfileViewController { enum Mode { case standard case premium } func enterMode(_ mode: Mode) { switch mode { case .standard: headerView.applyStandardAppearance() case .premium: headerView.applyPremiumAppearance() } }}
這你的同事只需要在內購成功回調中調用如下代碼即可:
func userDidUnlockPremiumSubscription() { profileViewController.enterMode(.premium)}
協議和私有類型
封裝代碼的另一個後方法是將協議和私有實現向結合。舉個例子:
加入我們正在構建一個需要在各種 ViewController 中載入大量圖像的 APP。為了解決這個問題,我們創建一個協議,該協議定義了 ImageLoader 我們 ViewController 可以訪問的 API:
protocol ImageLoader { typealias Handler = (Result<UIImage>) -> Void func loadImage(from url: URL, then handler: Handler)}
由於我們有很多的 ViewController 需要載入圖像,因此希望每個 ViewController 都是用自己的 ImageLoader,當 ViewController 銷毀的時候,需要取消未完成的請求。
為了很好的處理這個問題,我們使用工程模式構建一個 ImageLoaderFatory ,這樣可以方便的為每個 ViewController 構建 ImageLoader。ImageLoaderFatory 如下所示:
class ImageLoaderFactory { private let session: URLSession init(session: URLSession = .shared) { self.session = session } func makeImageLoader() -> ImageLoader { return SessionImageLoader(session: session) }}
我們注意到這裡並沒有 makeImageLoader 方法並沒有直接返回圖片載入器的具體類型,而是指明返回對象遵循 ImageLoader 這個協議。這樣我們就將 ImageLoader 具體實現隱藏起來了。
private extension ImageLoaderFactory { class SessionImageLoader: ImageLoader { let session: URLSession private var ongoingRequests = Set<Request>() init(session: URLSession) { self.session = session } deinit { cancelAllRequests() } func loadImage(from url: URL, then handler: (Result<UIImage>) -> Void) { let request = Request(url: url, handler: handler) perform(request) } }}
這樣做即給我們提供了很大的靈活性,同時也保持了API 非常簡明。
第三方依賴
上面的方法對封裝第三方庫也是非常的有用。
就像我們使用協議來隱藏具體的實現(SessionImageLoader)一樣,我們同樣可以使用協議來隱藏第三方庫,比如現在我們使用 AmazingImages 這個庫,我們可以用如下代碼進行封裝:
import AmazingImagesclass ImageLoaderFactory { func makeImageLoader() -> ImageLoader { return AmazingImageLoader() }}
這樣我們就只有一個文件使用到了 Amazing 這個庫,後續我們需要替換其他第三方庫也非常方便。
結論
項目越是複雜,應儘可能地封裝代碼---即使需要添加新功能,引入新的依賴關係或需求變更。這樣才能保持良好的體系結構。
原文:Code encapsulation in Swift
推薦閱讀:Swift 的 Enums 是 「Sum」 類型,這讓一切變得更有趣
極光日報,極光開發者旗下媒體。
每天導讀三篇英文技術文章。
推薦閱讀: