最近看了一些文章,發現有些人對這三個模式似乎仍有些誤解,之前曾經有寫過一篇這樣的文章,這回就再深入一點討論它們的差別吧。
之前寫的文章在:[Architecture] MVP, MVC, MVVM, 傻傻分不清楚~ | 小朱® 的技術隨手寫 - 點部落 (dotblogs.com.tw)
首先是 MVC,這個 1978 年提出的框架,它的重點在於分離程式邏輯、使用者介面與資料,在當時並沒有一個能奉為標竿的設計模式,因此 MVC 的提出影響了很多軟體開發的思維,包括程式碼的可重覆利用 (reusability),軟體開發能逐漸擺脫大部頭、義大利麵式的開發模式,也能有效提升軟體開發的產能。
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 變更具有即時反應能力的架構機能。