[Architecture] 再論 MVC, MVP 與 MVVM

最近看了一些文章,發現有些人對這三個模式似乎仍有些誤解,之前曾經有寫過一篇這樣的文章,這回就再深入一點討論它們的差別吧。

之前寫的文章在:[Architecture] MVP, MVC, MVVM, 傻傻分不清楚~ | 小朱® 的技術隨手寫 - 點部落 (dotblogs.com.tw)

首先是 MVC,這個 1978 年提出的框架,它的重點在於分離程式邏輯、使用者介面與資料,在當時並沒有一個能奉為標竿的設計模式,因此 MVC 的提出影響了很多軟體開發的思維,包括程式碼的可重覆利用 (reusability),軟體開發能逐漸擺脫大部頭、義大利麵式的開發模式,也能有效提升軟體開發的產能。

MVC架構與活動流程

MVC 的 Controller 負責的是接收、處理來自 View 的要求 (Request),而且會視情況向更後面的系統發出要求並接取回應,然後將回應組裝成一個回傳資訊,再回到 View 裡面做使用者介面的呈現,但是這個回傳資訊有可能是來自更後面的系統,也有可能是由 Controller 自行產生,但不論它來自哪裡,它都能稱為 Model,當 Model 回到 View 裡面後,View 就可以依據 Model 的內容去組裝呈現的結構,這時就不再需要 Controller 的介入,Controller 只需要等待下一個來自 View 的要求再接手處理即可。同時,因為 View 要接手 Model 對於介面的呈現,所以 View 內會包含與呈現有關的邏輯程式碼,而這些程式碼都會多多少少使用到 Model 的資料。

以 ASP.NET MVC (或 ASP.NET Core MVC) 來說,View 裡面就會包含很多腳本程式,這些程式會控制 Model 以產生對應的使用者介面或互動效果,但不包含任何商業邏輯,以下為一個 View 操作 Model 資料以呈現報表的範例。

@using WebPOS.ViewModels;
@{
    Layout = null;
    IEnumerable<ItemViewModel> items = (IEnumerable<ItemViewModel>)ViewBag.DifferenceItems;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>物流到貨狀態通知</title>
</head>
<body>
    <div>
        <p>您好,店櫃倉庫 @ViewBag.DestinationDepot 回報已收取由 @ViewBag.SourceDepot 發送的物流包裹 (編號:@ViewBag.Number,收貨人:@ViewBag.Employee)。</p>
        @if (items.Any())
        {
            <p>收貨結果系統發現有差異,列舉如下:</p>
            <table style="width: 100%; border: 1px solid black">
                <tr>
                    <th>條碼</th>
                    <th>出貨數</th>
                    <th>到貨數</th>
                    <th>差異數</th>
                    <th>備註</th>
                </tr>
                <tr>
                    @foreach (var item in items)
                    {
                        <td>@item.Barcode</td>
                        <td>@item.OutboundQty</td>
                        <td>@item.InboundQty</td>
                        <td>@(item.InboundQty-item.OutboundQty)</td>
                        <td>@item.DestinationComments</td>
                    }
                </tr>
            </table>
        }
        else
        {
            <p>收貨狀態正常無差異。</p>
        }

        <p>註:本信件由系統自動發出,如有疑問請洽物流作業人員,謝謝。</p>
    </div>
</body>
</html>
在 MVC 中,View 對 Model 是有控制能力的,但原則上應包含使用者介面的處理的邏輯 (UI Handling Logic),而非商業邏輯 (Business Logic)。

再來是 MVP (Presenter),在1998年被提出,既然被稱為 Presenter (主持人、呈現者),就一定是與 View 和 Model 間有直接相關的元件,這也是它和 MVC 最大的不同點,MVC 是由 View 來操作 Model,MVP 則是使用 Presenter,將 View 和 Model 整合,也就是說,View 怎麼和 Model 在使用者介面上關聯,是看 Presenter 怎麼做,View 只是 Presenter 的一個舞台而己。

Presenter 通常會以介面的方式,先行定義 View 與 Model 的關聯方式,而 View 會依照 Presenter 的指示做事 (也就是實作 Presenter 所定義的動作),然後在 Presenter 的實作上,將 View 的實作以及要關聯的 Model 以 DI (Dependency Injection) 的方式注入,然後在 Presenter 裡面做 data binding 或是事件處理 (如果有),最後呈現當然就是由 Presenter 操作的結果輸出,而不是經由 View。

想要更深入了解 MVP 實作方法的人,可參考這一篇文章:A Model View Presenter (MVP) implementation with ASP.NET | Rhames Consulting

MVP 模式在 View 與 Model 的關聯與使用者介面的處理上,以 Presenter 為主,View 只是被動 (Passive) 的被叫用,和 MVC 中 View 是主動的角色完全不同。
Windows Forms 本身其實就是一種類似 MVP 的架構,控制項都定義在 Designer.cs,而程式碼在各表單的 .cs 中對控制項做操作,所有的控制項都聽命於 Form,但它只是類似,和 MVP 所要求的還是有相當的差距。

MVVM (ViewModel),於 2005 年被提出,它和 MVP 的差異是,MVVM 將 View 的控制權還給了 View,但是將 Model 以 ViewModel 的控制方式隔離開,並且開放一個通知的機制 (以 WPF 來說是 INotifiyChanged) 來通報 View,Model 端有做改變,相對的 View 也可以告訴 ViewModel 使用者介面端的資料有改變,讓 ViewModel 可以通報 Model 端做更新。

MVVM 雖然是 Microsoft 於 2005 年針對 WPF 的特性所提出的架構,但是這個架構反而被廣泛運用在 JavaScript 的前端框架,目前主流的前端框架都有實作具有 MVVM 特性的機制,不論是 Angular、React 或是 Vue.js 皆然,不論是 JSX、Virtual DOM 機制,其特性都和 MVVM 的 ViewModel 相似,因此 JS 只需要操作資料變數,就能反應在 HTML 的使用者介面上,減少許多要撰寫 DOM 溝通機制的程式。

MVVM 將 View 和 Model 以 ViewModel 隔開,以實作出對 Model 變更具有即時反應能力的架構機能。