理解並運用MVC,MVP,MVVM

钰琪發表於2024-05-11

前言

MVC,MVP,MVVM 屬於 GUI 軟體設計,它們都強調將軟體的檢視顯示與業務邏輯進行分離,將軟體拆分為三個部分,分別負責資料操作、檢視邏輯、業務邏輯。

業務邏輯指的是任何與資料操作、檢視操作的定義與實現無關的功能。
具體來說,業務邏輯中不應該出現如何操作檢視或如何運算元據的細節。
對於這些操作,應該在檢視層或資料層定義相關的功能,在這些功能內實現相關操作,再由業務邏輯去呼叫這些功能的 API。

這三個部分對程式有一個簡單的劃分:

  • Model 負責維護軟體的資料,實現那些處理資料的功能,供外部呼叫,它保持獨立,不依賴 View 與 Controller。
  • View 負責使用者介面,將資料展示使用者,實現那些操作檢視的功能,供外部呼叫。
  • Conttoller/Presenter/ViewModel:
    負責實現軟體的業務邏輯,協調 View 與 Model,將 Model 的資料展示到 View 的使用者介面,並將 View 對資料的更改更新至 Model。

MVC

MVC 將軟體按功能進行拆分,但是對各部分的職責、各部分的協作關係沒有很明確的約束。

根據職責與關係的不同,MVC 也有不同的實現。

Passive View MVC

被動檢視指得是 View 的使用者介面被動的等待更新,它不依賴 Model 來運算元據,將 View 資料的初始化、更新以及對 Model 資料的更新交給 Controller。

這樣的定義使得 Model 與 View 都是獨立的,可被複用。

那 Model, View, Controller 具體該承擔什麼樣的職責呢?

  • Model 的職責很明確,提供運算元據的功能。

  • Passive View 呢?是不是意味著著它只能定義如何操作檢視,但不能自行操作呢?
    不然,對於那些隻影響檢視,不涉及 Model 資料操作的檢視操作,應該由 View 自行處理,沒有必要再委託 Controller 來呼叫。而那些與 Model 資料相關的檢視操作,可以由 View 封裝好供 Controller呼叫。比如獲取資料要訪問網路,屬於耗時操作,過程中要展示進度條,那針對進度條的操作就應該封裝為 API 以供外部呼叫

  • Controller 負責具體的業務邏輯。對於 View 中響應使用者輸入的功能,凡是涉及業務邏輯的,View 自己不實現,而是在 Controller 中實現,View 去呼叫。

View 可以持續響應使用者的輸入,如果將所有的業務邏輯都放在 Controller 中,會使得 Controller 任務繁重。
雖然可以透過將 Controller 拆分為多個子模組來解決這個問題,但是思考如何為各個子模組分配職責也是個麻煩事兒。另外, 對 Model 與 View 呼叫可能會分散在程式碼的許多地方,難以管理。

要解決 Controller 任務重的問題,可以將部分業務邏輯轉移出去,比如 Model 與 View 之間的資料同步邏輯是可以放在 View 中。

要實現資料的同步,View 需要知道 Model 的資料變化,而 Model 因為不能依賴 View,也就不能去更新 View。要解決這個問題,可以使用觀察者模式。

Observer Pattern

引入觀察者模式,將 Model 定義為可觀察物件,這樣 View 就可以在 Model 資料更新後更新使用者介面了。除了 View, Controller 也能向 Model 註冊觀察者,執行一些與使用者介面無關的操作。

Active Model MVC / Active View MVC

View 可以主動的更新檢視,Model 也可以將自身變化主動的通知觀察者,應用觀察者模式的 MVC 可以被稱作 Active View MVC 或 Active Model MVC。

View 與 Model 之間的資料同步操作,可能是簡單的,比如將資料原封不動地傳遞,或者進行資料拼接、型別轉換;
也可能是複雜的,比如類似加解密這樣的複雜計算,訪問網路處理資料,呼叫第三方軟行處理資料,之後再進行同步操作。

不可能將所有的資料同步邏輯都轉移到 View 中,好的做法是將簡單的同步操作放在 View 中實現,複雜的在 Controller 中實現。

WEB MVC

在伺服器端 WEB 軟體開發也有 MVC 的概念,但是 View 往往只是一個 HTML 文件, 不能響應使用者輸入,但瀏覽器根據 HTML 來渲染介面,倒是也符合 MVC 對 View 的定義。

MVP

MVP 就是 MVC,MVP 有兩種實現定義:

  1. Passive View MVP: 就是 Passive View MVC
  2. Supervising Controller MVP: 就是 Active View MVC,是將簡單的同步操作放在 View 中實現,複雜的在 Prennter 中實現

真要說有什麼不同,可能就是 MVP 強制約定了 View 與 Model 的關係,而 MVC 對此並無限制。

在 OOP 專案實踐中,View 被定義為介面,而 Presenter 被定義為類,View 被注入到 Presenter 中,這樣可以保證 View 與 Presenter 能被一起復用。

MVC / MVP 程式碼示例

Android Java 虛擬碼:

interface IView {
  public void showPeogressbar();
  public void cancelPeogressbar();
}
class Presenter {
  private Iview view;
  void Presenter(IView v){
    this.view = v;
  }
  
  public void logic() {
    this.view.showPeogressbar();
    // dosth
    this.view.cancelPeogressbar();
  }
}

class MainActivity implements IView() {
  private Presenter presenter;
  public void onCreate() {
    this.presenter = new Presenter(this);
    findViewById(R.id.btn).setOnclickListente((View view)->{
      this.presenter.logic();
    });
  }
  
  public void showPeogressbar() {}
  public void cancelPeogressbar() {}
}

MVVM

MVC 和 MVP 本質上是一樣的,而 Model-View-ViewModel(MVVM) 也是將軟體分為三個部分,ViewModel 與 MVC 的 Controller 職責也是一樣的,負責業務邏輯。MVP 並沒有給MVC引入什麼新的概念,但 MVVM 引入了新的概念:

  • 將檢視從業務邏輯中分離
  • 檢視狀態
  • 資料驅動檢視

檢視的狀態

  • 檢視中會變化的資料
  • 檢視用於響應使用者輸入的業務邏輯,即檢視的行為

檢視的狀態的本質是給檢視引入了一個代理,即 ViewModel,透過代理來間接操作檢視。

分離檢視與業務邏輯

將檢視從業務邏輯中分離的核心是業務邏輯中不操作檢視,只運算元據,在業務邏輯中不會去訪問檢視的細節。分離檢視的方式是實現 資料驅動檢視

資料驅動檢視

資料驅動檢視的直觀表現是,改變資料,檢視就更新。其原理是 ViewModel 維護一個資料集合,與 View 的狀態一一對應。本質上還是觀察者模式,View 透過觀察 ViewModel 的資料變化,來更新檢視。而 View 因使用者輸入產生的狀態變化也會主動更新到 ViewModel 的資料集合中。

需要注意的是,ViewModel 名字裡的 'Model' 指得是 View 的狀態所對應的資料,而與 Model 無關,View 狀態的更新不會同步到 Model 中。
更改 ViewModel 的資料以更新 View 狀態,屬於檢視邏輯,而同步 View 狀態對應的資料到 Model中是業務邏輯,我們還要自行實現。

宣告式 UI

檢視的資料和行為的繫結操作不會憑空就能從業務邏輯中分離,我們肯定要寫程式碼來實現,但是也不可能每個 View 都先編寫一個 ViewModel,那樣會有太多的樣板程式碼。

既然寫程式碼麻煩,那就生成程式碼。使用特定的文字格式,或者說標記語言來宣告檢視,在宣告中為檢視繫結 ViewModel,為控制元件繫結狀態(資料和行為),然後寫一個解析器來生成程式碼。

不同的開發平臺有不同的實現,比如 Javascript Web 應用使用 HTML 或者 JSX 宣告檢視,Android 使用 XML 或 Compose 來說明檢視,而微軟的 WPF .NET 使用 xaml, Blazor 使用 razor。

使用標記語言來定義檢視,真正做到了將檢視的關注點從業務邏輯中分離,因為解析器只用編寫一次,算不得業務邏輯。

一切皆資料

資料驅動檢視的開發方式已經是所有追求先進性的 GUI 軟體開發框架所必備的了。

在 ViewModel 中是沒有 View 的細節的,只有 View 的狀態所對應的資料。開發業務邏輯的過程中可以完全當檢視不存在,一切都是資料。

相關文章