iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 19
0

作為 architecture pattern 三兄弟最晚出生的小弟,MVVM (Model-View-ViewModel) 卻在 Android 平台上成為 Google 大力支持的後起之秀。
先讓我們來看看 MVVM 所定義的階層以及關係:

MVVM

在 MVVM 的設計當中,依賴性的關係完全是單向的:View 認識 View Model、View Model 認識 Model,反向的關係則不存在。
階層當中的實線與虛線分別代表依賴以及觀察,View 直接依賴於 View Model 實體,呼叫其提供的介面。並透過觀察 View Model 的更新,來決定自己對應的行為;View Model 對 Model 也有著同樣的關係。
Model 本身可以完全獨立於其他兩個層級存在;而 View Model 也獨立於 View 存在,要更換 View 實作時不容易觸動到其他地方。

  • Model: 與另外兩個 patterns 當中的 Model 類似,需要提供可被觀察的介面。

  • View: 接收使用者輸入並通知 View Model;觀察 View Model 以更新畫面。

  • View Model: 在實現箭頭方向的部分 (依賴),負責接收使用者輸入,並轉換成對於 Model 的更新。在虛線箭頭方向的部分(觀察),將 Model 及畫面呈現所需的狀態 (view state) 封裝並提供可被 View 觀察的介面。

Android 範例

code example 中提供了三種 MVVM 的實作,分別為:

  • 使用 Androidx 提供的 LiveData 以及 ViewModel 進行實作。
  • 使用 RxJava 進行實作,並將情境分為多個 ViewModel。
  • 使用 RxJava 進行實作,並將情境集中在一個 ViewModel。

第一種實作只是自己在測試 Google 所提供的支援,主要想討論的是將情境 "分開或是集中" 的這個選擇。

在 MVVM 中,我們常會用一個 View Model 來封裝一個使用情境、或著對於同一個 Model 進行操作的多個情境。這裡以 "瀏覽單篇文章以及返回" 的情境舉例來說:在顯示單篇文章內容時出現返回鍵,這裡的兩個行為 "單篇文章的顯示或隱藏"、"返回按鍵的隱藏或顯示" 被視為同一個情境。

MVVM one scenario

從圖中可看出,View Model 層處理的事情只有選擇文章的行為,而返回按鍵的隱藏或顯示則是完全由 View 層決定。

但如果有新的需求希望改變返回鍵的行為:

  • 總是顯示
  • 在瀏覽單篇文章時點擊,則隱藏單篇文章
  • 在瀏覽文章列表時點擊,則返回上一頁

此時我們可能就會想要將原本的情境切割成兩個,並由不同的 View Model 來實現:

MVVM two scenarios

在這種實作中,BackViewModel 扮演了有趣的角色,在 "隱藏單篇文章" 的行為上,他更改了 Model 的值,這個結果會藉由 SelectedArticleViewModel 被 View 觀察到。
而在 "返回上一頁" 的行為上,則是直接被 View 觀察並執行。

比較這兩種不同的實作方式,我們可以看到雖然 View Model 有所不同,但需要的 Model 完全相同;在多個 View Model 的實作當中,各個 View Model 透過觀察而能夠隨時得知 Model 的最新狀態,彼此之間不需要有任何依賴以及溝通。

Architecture Patterns 兄弟之間的比較

  • 依賴關係

    在 MVC (Model2) 或是 MVP 中,Controller, Presenter 需要扮演主動串流事件的角色,所以與 View 之間存在著依賴性。且就是因為這樣的關係,在擴充情境時很可能需要額外拉出更多高度依賴的連結,或是主動通知資料更新的行為,讓系統複雜程度持續上升。

    MVVM 善加利用了觀察者模式,明確限制各層級之間的依賴關係以及溝通方式,以換取層級之間職責區分更加明確。

    三個層級就像是 clean architecture 的洋蔥圖一般:Model 作為最核心的存在,對外層一無所知;View Model 層定義了各種使用情境以及其串流,包括預期從外層被呼叫的介面、以及對內層需要依賴的 Model;View 作為最外層,實作了所有畫面顯示和使用者輸入的需求。

    一旦任何 Model 層的資料有所更新,所有相關的 View Model 便會自動運作起來,為自己的情境進行所需的更新。View Models 之間不需要知道彼此,也能順利的完成合作。

  • 畫面狀態 (View State)

    在 MVC 以及 MVP 當中,沒有明確定義畫面狀態的存在。在傳統的 MVC 當中,所有的狀態都需要創造 Model 來紀錄,而在 MVP 中,可能有部分畫面狀態會散落在 Presenter 內部。但在這兩種 patterns 的實作上,更多時候我們會不小心讓這些狀態散落在 View 當中。

    相較之下 MVVM 將畫面狀態封裝在 View Model 層,提供了一個合理放置畫面狀態的地方,既不會污染 Model 也不會讓狀態到處散落。

MVVM 的優勢及缺陷

  • 優勢

    1. 完整的關注分離及明確的分層
    2. 單向性的依賴關係
    3. 透過 View Model 封裝畫面狀態
    4. 善於處理串流資料 (streaming data):使用觀察者模式所帶來的另一個好處,便是觀察者預期得到數量未知且持續發出的資料,所以觀察者模式的實作,經常能夠提供許多幫助處理串流的方法。例如因為不希望 "連續的點擊造成短時間內多次觸發事件" 時,可以使用反彈跳時間 (debounce) 功能來限定事件在一定時間內傳遞的次數。
  • 缺陷

    1. 實作細節深入系統:不管是使用 LiveData 或是 RxJava 的實作方式,需要實踐完整的觀察者模式時,這些實作都會進入系統中的每一層。
    2. 高學習成本:你有看過雙層的 Observable 嗎 (?)
    3. 各層之間的程式碼追蹤
    4. 同步或非同步、以及 Thread 管理
    5. 觀察或不觀察:雖然觀察者模式提供了很方便的方式將資料的更新轉為可被觀察的狀態,但我們不會總是使用 "觀察" 的方式接起所有串流。例如:觀察到背景更新時需要更新前景、觀察到前景更新時就順帶更新背景,兩個規則加起來會導致畫面不必要的持續更新。
    6. 看不見的連結以及記憶體消耗:以 RxJava 的實作為例,其底層仍然是以 callback 作為基底,而在觀察者新增的同時,處理持續發出的資訊以及上下游的連結等都會需要消耗記憶體。

結語

MVVM 在設計上有著相當多巧思,為其帶來了許多特別之處。他相當符合 clean architecture 的原則、同時支持關注分離及單向性、分層時的限制也為團隊合作帶來了規範。但此同時,我們也必須看到他所帶來的 trade-off,包括高學習成本,以及與實作選用方式的高依賴等等,都是需要整個團隊長期投入成本才能消化的。

雖然在 MVC, MVP, MVVM pattern 三篇文章中都描寫了優勢及缺陷,但對筆者來說他們並沒有絕對的好壞,在選用時還是會以 pattern 的特性、開發當下的需求、未來發展性以及維護成本、團隊組成等去思考。

而在這三個 architecture pattern 之外,也有著像是 MVI, Redux ... 等更多的選擇,相信未來也會持續出現各式各樣的 pattern。
總的來說,architecture pattern 們都希望能夠 "增加一定程度的限制,來換取開發時的一致性",透過定義 "哪個元件/階層的責任是什麼" 來限制元件的行為、以及階層之間的溝通方式。

開發團隊在選用 architecture pattern 合作時,一定程度的開發成本綁定在每位成員對於 pattern 本身的認知。所以在引用 pattern 時,需要盡量避免以自己的解讀破壞原有的限制,才能夠真正帶來團隊合作上的幫助。

三篇 architecture patterns 的介紹到此結束,希望有幫助大家發現一點新東西!

作者:Yolung


上一篇
[Architectural Pattern] MVC pattern for Android
下一篇
[Architectural Pattern] 服務導向架構 Service-Oriented Architecture 波折的一生
系列文
什麼?又是/不只是 Design Patterns!?32

尚未有邦友留言

立即登入留言