單一職責原則在 iOS 中的應用

知識小集發表於2019-02-27

原文連結

本文翻譯自 “Cleaner Architecture on iOS”, 作者 Tomas Hakel

這篇文章不打算講一些新的或有創新性的東西,而是來討論一個廣為人知的東西:單一責任原則(SRP)。更具體的說,我想討論的是如何在 Clean Architecture 中來正確地使用它。我們應該經常提醒自己,在做決策時考慮一下 SRP,以此幫助我們設計出更好的軟體。

單一職責原則在 iOS 中的應用

SRP 是什麼?

先來看看 WIKI 上的定義:

The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

單一責任原則是一種計算機程式設計原則,規定每個模組或類只對軟體提供的功能的一部分負責,並且該職責應完全由類封裝。其所有服務應與該職責嚴格一致。

一個類應該只有一個改變的因素。遵循這個原則,可以獲得諸多好處:使修改變得更容易、減少耦合、提高可測試性、加快開發速度等等。SRP 也是 Clean Architecture 的基本思想(適用於 MVC、MVVM、響應式等等)。

為什麼不使用 MVC?

最直接的答案是:Massive View Controller。儘管這只是個玩笑話,不過實際情況確實如此。這也引出了另一個問題:為什麼 MVC 是 iOS 上的預設架構,即便它會導致諸如 Massive View Controller 這樣的問題?答案是,它不是首要問題。如果 Controller 很龐大,這並不是架構的錯,而是程式設計師沒有正確地使用它。你可以使用 MVC 編寫出結構非常清晰的程式,同時可以通過以下方法輕鬆處理 Massive View Controller 問題:

  • 一個場景使用多個控制器;
  • 將任務委託給 worker/service 類;

一句話:使用 SRP。那麼,如果 MVC 不是問題,那為什麼要使用其它框架呢?原因是正如在 iOS 上的應用一樣,MVC 存在一些問題,這使它會界定職責時比較模糊,留給程式設計師做的決定太多。比如:控制器的責任是什麼?如果你不小心一點,那它所承擔的就很多。那我們應該把一些需要的工作放在哪裡呢?在 model 層?我們又如何去構建?MVC 並沒有明確的告訴我們如何去做。這就意味著我們有很大機率引入錯誤。

如果我們不想考慮這些問題,就可以使用 Clean Architecture。Clean Architecture 明確劃分了類的職責:Presenter 負責橋接 UI 層和業務邏輯層;Interactor 處理我們的用例;路由幫我們跳轉到新頁面。權責清晰,這樣我們的程式碼就會更乾淨整潔。

那麼,使用 Clean Architecture,可以解決 MVC 的問題麼?在我們的程式碼中應用了 SRP 了麼?不一定。這個架構非常有用,但並沒有解決所有問題。我們仍然需要思考、做出選擇,並努力讓程式碼變得更加清晰。

MIP 問題

在 Apple 平臺上,Clean Architecture 已經非常流行。我們有很多種選擇,如 VIPERClean Swift。在這裡讓我們來看一些 Clean Swift 的實際例子。

我見過一些有大型的複雜 Interactor 的工程,這些工程並不遵循 SRP。我稱之為 Massive Interactor Problem(MIP)。MIP 可能並不像 Massive View Controller 問題那麼糟糕,因為 Interactor 並不關心 UI,但它依然試圖去做太多的事情。問題在於,儘管我們認為自己正在使用 Clean Architecture,但並沒有恰當地去劃分職責。為了從根本上避免 MVC / MIP 問題,我們需要更多地使用 SRP。

Interactor 包含了應用程式的業務邏輯,但每個 View Controller 只有一個 Interactor。在小場景中,這沒有問題,但正如在一個場景中只使用一個 View Controller 一樣,在大的場景中,Interactor 同樣面臨著失控的局面。我的觀點是,在複雜的場景中,Interactor不應該真正去處理任何事情:沒有演算法,沒有資料庫操作或資料解析,沒有任何需要巢狀的 if 或迴圈語句。這些任務應該由 Worker 類來處理。Interactor 的職責是將任務交給各種 Worker ,並把結果傳遞給 Presenter。Clean Swift 的示例程式碼中,Worker 只包含一小段程式碼,由於比較簡短,所以沒有體現出它的重要性。示例這麼做是沒錯的,畢竟 Worker 的職責是處理特定的業務邏輯。不過,我覺得需要特別強調一下 Worker 的重要性。

在 Clean Architecture 中,一個 Interactor 應該表示一個單獨的用例。這意味著一個類應該只有一個關注點。在 Clean Swift 中,一個場景只有一個 Interactor,而一個場景通常會包含多個用例,所以 Interactor 會有很多職責。在問題比較簡單時,我們可以選擇忽略這一點,但隨著 Interactor 的增長,它會變得越來越不受控制。另一種選擇是將這些職責代理給 Worker 類。在這種情況下,Interactor 將建立多個 Worker。當 UI 發起一個請求時,Interactor 將簡單地將請求傳送給 Worker 處理,並將結果傳回給 Presenter。這聽起來很簡單,但卻大大改善了 MIP 問題。由於一個 Worker 只處理用例,它看上去更像一個 Interactor,不是麼?

VIPER 是什麼?

我聽很多人說 VIPER 很複雜:需要花很長的時間去配置,有太多很小的類。但 VIPER 很好的實踐了 SRP。如果你更關注 SRP,並將 SRP 應用於你的 MVC 工程,最後也可能會產出類似的框架(類似的事情就發生在我身上)。

Clean Swift 和 VIPER 非常相似,畢竟都是基於 Clean Architecture。主要的區別在於 VIP cycle,以及每個場景的 Interactor 的數量。但如果我們在 Clean Swift 中嚴格遵循 SRP,將 Clean Swift Interactor 的職責劃分清楚,併為每個用例建立一個 Worker 類,基本上就是 VIPER 了(如果我們忽略一些細節,就可以將 Clean Swift 的 Worker 與 VIPER 的 Interactor 對應起來,而 Clean Swift 的 Presenter-Interactor 可以對應 VIPER 的 Presenter)。事實上,由於有 VIP cycle,Clean Swift 顯得更復雜。VIPER 並不像人們所說的那麼複雜。所以不要擔心它所提供的東西。即使在下一個專案中你不用使用 VIPER,但去理解它,以及它的運作方式,也是大有益處的。而使用它,可以讓自己和他人的工作更加輕鬆。

More on SRP

這裡有一些建議:

  • 不要混淆抽象層。在上層業務邏輯中執行底層操作是違反 SRP 原則的。畢竟我們在處理組織或器官時,不希望去考慮細胞的複雜性,反之亦然;
  • 明確類的職責,並將其寫在類上方的註釋中。描述應簡明扼要,同時包含所有職責。不過要留心 “manager” 這樣的詞。manager 用來做什麼?如果不注意的話,這樣的類很容易包含多個職責。當修改類時,請確保它的職責不變,否則就重構它;
  • SRP 可應用於多個級別,從通用架構到單個方法。如果一個方法做了多件事,就將這些操作分解幾個新方法中,讓每個方法執行特定的任務,然後在原方法中呼叫。記住,一個方法應該只在一個抽象層中;
  • 大段的程式碼意味著破壞了 SRP。如果你發現在 IDE 中滾動了很長時間,那麼程式碼可能出問題了。SRP 是一個比行數更好的度量標準,所以不要找一個魔數來規定一個檔案中程式碼的行數,而是列出職責。如果不止一個,就進行拆分。較短的類和方法也更容易維護,所以儘量保持合理的程式碼長度;
  • 務實,而不是完全遵循規範和指南,瞭解為什麼這樣設計以及想要達到的目標。然後做出明知的選擇。

總之,不要寫出 Massive Controllers。明確職責,確保在一個地方沒有太多的職責(理想情況下,2個就是太多了)。在為類新增程式碼時,需要考慮類的職責,而不是簡單的新增。從長遠來看,遵循 SRP 最有可能節省時間 - 所以不要找藉口。

資源

  1. en.wikipedia.org/wiki/Single…
  2. 8thlight.com/blog/uncle-…
  3. clean-swift.com/clean-swift…
  4. objc.io/issues/13-a…

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題。可以公眾號留言 iosflutterwebpwa小程式 等關鍵詞獲取入群方式。

單一職責原則在 iOS 中的應用

相關文章