溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解MVC及其變種

發(fā)布時間:2021-10-14 11:27:35 來源:億速云 閱讀:137 作者:iii 欄目:開發(fā)技術

這篇文章主要介紹“如何理解MVC及其變種”,在日常操作中,相信很多人在如何理解MVC及其變種問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解MVC及其變種”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

創(chuàng)建可維護的應用始終是構建應用的真正的長期挑戰(zhàn)。

不久以前,我還為一家公司工作過,其核心業(yè)務應用是擁有數(shù)千家公司客戶的 SaaS 平臺。這個至關重要的應用已經開發(fā)了三年,代碼文件中混雜著  HTML、CSS、業(yè)務邏輯和  SQL。果然,在發(fā)布兩年之后,公司決定完全重寫這個應用。盡管這些情況時有發(fā)生,但如今我們許多人都知道這是不對的以及該如何避免。

然而,在20世紀70年代,職責混雜還是常見的實踐,人們還在尋找更好的解決辦法。隨著應用程序復雜度的增長,修改 UI  必然也會引起業(yè)務邏輯的修改,修改越發(fā)復雜,耗費的時間也越來越多,還可能帶來更多的問題(因為修改的代碼更多了)。

MVC 因此應運而生,它提出前端和后端之間的“關注點分離”來解決上述問題。

1979 – Model-View-Controller

如何理解MVC及其變種

為了解決上述問題,Trygve Reenskaug 于1979 年提出了 MVC 模式來分離關注點,將 UI 和業(yè)務邏輯隔離。該模式當時被應用于1973  就已經出現(xiàn)的桌面圖形界面的開發(fā)。

MVC 模式將代碼拆分成了三個概念單元:

  • 代表業(yè)務邏輯的 Model (模型);

  • 代表 UI 控件的 View (視圖):按鈕、文本框等等;

  • 在視圖和模型之間居中協(xié)調的 Controller(控制器),這意味著:

    • 它決定顯示哪些視圖以及哪些數(shù)據(jù);

    • 它將用戶操作(例如點擊按鈕)轉換成業(yè)務邏輯。

模型可以是單個對象(相當無趣),也可以是對象組成的某種結構。——Trygve Reenskaug 1979, MVC

最初的 MVC 模式還有其它一些需要了解的的重要概念:

  • View 直接使用 Model 數(shù)據(jù)對象來展示數(shù)據(jù);

  • 當 Model 發(fā)生變化時,會觸發(fā)一個事件立即更新 View(記住,1979年還沒有 HTTP);

  • 每一個 View 通常只關聯(lián)一個 Controller;

  • 每個界面可以包含多對 View 和 Controller;

  • 每個Controller 可以對應多個 View。

現(xiàn)在我所熟知的 HTTP 請求響應范式并沒有使用最初的 MVC 風格。這是因為,按照原始的設想,數(shù)據(jù)從 View 流向  Controller,這和我熟悉的一樣,但另一邊,數(shù)據(jù)直接從 Model 流向 View,并沒有經過 Controller。

而且,在現(xiàn)在的請求響應范式中,當數(shù)據(jù)庫中的數(shù)據(jù)發(fā)生變化時,并不會觸發(fā)瀏覽器中展示 View 的更新(盡管可以用 Web Socket  實現(xiàn))。要看到更新后的數(shù)據(jù),用戶需要發(fā)起一次新的請求,而更新的數(shù)據(jù)總是會通過 Controller 返回。

1987/2000 – PAC/Hierarchical Model-View-Controller

如何理解MVC及其變種

PAC 又稱 HMVC,在 UI 片段控件化的上下文中它能帶來更好的模塊化拆分。

例如,我們會發(fā)現(xiàn) View 的一部分被其它一些 View 以同樣的格式使用,甚至直接就在同一個 View 重復使用。一個實際的例子就是網頁展現(xiàn) RSS  訂閱內容的片段,它可以被其它頁面重用。

如果使用 HMVC,處理主請求的 Controller 會將子請求轉發(fā)給其它 Controller 讓這些控件進行渲染,然后在主 View  的渲染中合并它們。

在 HTTP 請求/響應范式的上下文里,我自己也曾遇到過幾次這種情況,但我發(fā)現(xiàn)了一個更簡單的方法,即讓 UI 向可以渲染控件的 Controller 發(fā)起  AJAX 調用。在保持模塊化優(yōu)勢的同時并沒有增加嵌套 Controller 調用帶來的復雜性,另一個優(yōu)勢就是這些子請求可以使用像 Varnish  這樣的緩存。

1996 – Model-View-Presenter

如何理解MVC及其變種

MVC 模式給當時的編程范式注入了一劑強心針。然而,隨著應用程序復雜度的增加,需要更進一步地解耦。

1996 年,IBM 的子公司 Taligent 公開了他們基于 MVC 的 模式 MVP。其思想是將 Model 對 UI 的關注更徹底地分離:

  • View 是被動的,對 Model 無感知;

  • 專注于輕量 Controller(Presenter),它們不包含任何業(yè)務邏輯,只是簡單地調用命令和/或查詢模型,將原始數(shù)據(jù)傳遞給 View;

  • 數(shù)據(jù)的變化不會直接觸發(fā) View 的更新:它始終要通過 Presenter,由 Presenter 來更新 View。這樣在更新視圖之前  Controller(Presenter) 還可以執(zhí)行一些和展現(xiàn)相關的額外邏輯。例如,同時更新另一些數(shù)據(jù),它們和數(shù)據(jù)庫中發(fā)生變化的數(shù)據(jù)有關;

  • 每個 View 對應一個 Presenter。

這更接近我所見到的現(xiàn)在的請求/響應范式:數(shù)據(jù)流始終要經過 Controller/Presenter。不過,Presenter  仍然不會主動更新視圖,它始終需要執(zhí)行一次新的請求才能讓變化可見。

MVP 中的 Presenter 又被稱為 Supervisor Controller。

2005 – Model-View-ViewModel

如何理解MVC及其變種

由于應用程序的復雜性還在增加,2005 年微軟的 WPF 和 Silverlight 架構師 John Gossman 又提出了 MVVM  模式,目標是進一步將 UI 設計從代碼中分離出來,并提供 View 到數(shù)據(jù)模型的數(shù)據(jù)綁定機制。

[MVVM] 是 [MVC] 的變種,專為現(xiàn)代 UI 開發(fā)平臺設計?,F(xiàn)代 UI 開發(fā)中,View 是由設計師負責而不是由傳統(tǒng)意義上的開發(fā)者負責。[…]  開發(fā)應用程序 UI 使用的工具、語言以及使用它們的人都和業(yè)務邏輯以及數(shù)據(jù)后端有著天壤之別。——John Gossman 2005, Introduction  to Model/View/ViewModel pattern

Controller 被 ViewModel “取代”:

[View] 對鍵盤快捷鍵進行編碼,而且控件自行管理與輸入設備的交互,這本該是 MVC 中的 Controller 的職責(現(xiàn)代 GUI 開發(fā)中  Controller 的變化說來話長...我認為它只是淡出了開發(fā)者的實現(xiàn)。它始終都存在著,而我們不需要像1979年那樣去思考它)。——John Gossman  2005, Introduction to Model/View/ViewModel pattern

MVVM 背后的思想是:

  • ViewModel 和 View 一一對應;

  • 將 View 中的邏輯轉移到 ViewModel 來簡化 View;

  • View 使用的數(shù)據(jù)和 ViewModel 中的數(shù)據(jù)一一對應;

  • 將 ViewModel 中的數(shù)據(jù)綁定到 View 中的數(shù)據(jù)上,這樣 ViewModel 中數(shù)據(jù)的變化會立即體現(xiàn)在 View 上。

和最初的 MVC 模式的情況相仿,對傳統(tǒng)的請求/響應范式來說這種方法是行不通的,因為 ViewModel 無法主動地更新 View(除非使用 Web  Socket),而 MVVM 對這一點是有要求的。還有,根據(jù)我的經驗,ViewModel 的屬性和 View 使用的數(shù)據(jù)做到完全匹配并不是 Controller  的常見實踐。

Model-View-Presenter-ViewModel

如何理解MVC及其變種

當構建云原生的復雜企業(yè)應用時,我傾向于將應用的 UI 結構合理地設計成 M-V-P-VM,這里的 View Model 是 Martin Fowler 在  2004 年提出的 Presentation Model,。

Model

一組包含業(yè)務邏輯和用例的類。

View

一個模板,模板引擎用它來生成 HTML;

ViewModel(又叫做 Presentation Model)

從查詢中接收(或者從 Model 實體中提取)原始數(shù)據(jù),持有這些會模板會用到的數(shù)據(jù)。它還要封裝復雜的展現(xiàn)邏輯,來簡化模板。我發(fā)現(xiàn)運用 ViewModel  十分重要,因為我們絕不會想在模板中使用實體。這樣我們才能將 View 和 Model 完全隔離開:

  • Model 中的變化(比如實體結構的變化)會上升并影響 ViewModel,但不會影響模板;

  • 復雜的展現(xiàn)邏輯被封裝到了 ViewModel 之中,因此不會被泄露(例如,在業(yè)務實體中創(chuàng)建一些只和展現(xiàn)邏輯有關的方法)到領域之中;

  • 模板的依賴變得很清晰,因為它們必須在 ViewModel 中設置。例如,暴露出依賴可以幫助我們決定應該優(yōu)先從數(shù)據(jù)庫中加載哪些內容來避免 N+1  問題。

Presenter

接收 HTTP 請求,觸發(fā)命令或查詢,使用查詢返回的數(shù)據(jù)、ViewModel、模板和模板引擎生成 HTML 并將它返回給客戶端。所有 View  的交互都要經過 Presenter。

下面是我實現(xiàn)的一個非常簡單的例子:

<?php // src/UI/Admin/Some/Controller/Namespace/Detail/SomeEntityDetailController.php namespace UI\Admin\Some\Controller\Namespace\Detail; // use ... final class SomeEntityDetailController {     /**      * @var SomeRepositoryInterface      */     private $someRepository;        /**      * @var RelatedRepositoryInterface      */     private $relatedRepository;     /**      * @var TemplateEngineInterface      */     private $templateEngine;     public function __construct(         SomeRepositoryInterface $someRepository,         RelatedRepositoryInterface $relatedRepository,         TemplateEngineInterface $templateEngine     ) {         $this->someRepository = $someRepository;         $this->relatedRepository = $relatedRepository;         $this->templateEngine = $templateEngine;     }     /**      * @return mixed      */     public function get(int $someEntityId) {         $mainEntity = $this->someRepository->getById($someEntityId);         $relatedEntityList = $this->relatedRepository->getByParentId($someEntityId);         return $this->templateEngine->render(             '@Some/Controller/Namespace/Detail/details.html.twig',             new DetailsViewModel($mainEntity, $relatedEntityList)         );     } }

M-V-C-VM_-_Controller_example.php

<?php // src/UI/Admin/Some/Controller/Namespace/Detail/DetailsViewModel.php namespace UI\Admin\Some\Controller\Namespace\Detail; // use ... final class DetailsViewModel implements TemplateViewModelInterface {     /**      * @var array      */     private $mainEntity = [];     /**      * @var array      */     private $relatedEntityList = [];     /**      * @var bool      */     private $shouldDisplayFancyDialog = false;     /**      * @var bool      */     private $canEditData = false;     /**      * @param SomeEntity $mainEntity      * @param RelatedEntity[] $relatedEntityList      */     public function __construct(SomeEntity $mainEntity, array $relatedEntityList) {         $this->mainEntity = [             'name' => $mainEntity->getName(),             'description' => $mainEntity->getResume(),         ];         foreach ($relatedEntityList as $relatedEntity) {             $this->relatedEntityList[] = [                 'title' => $relatedEntity->getTitle(),                 'subtitle' => $relatedEntity->getSubtitle(),             ];         }                  $this->shouldDisplayFancyDialog = /* ... some complex conditional using the entities data ... */ ;                  $this->canEditData = /* ... another complex conditional using the entities data ... */ ;     }     public function getMainEntity(): array {         return $this->mainEntity;     }     public function getRelatedEntityList(): array {         return $this->relatedEntityList;     }     public function shouldDisplayFancyDialog(): bool {         return $this->shouldDisplayFancyDialog;     }     public function canEditData(): bool {         return $this->canEditData;     } }

M-V-C-VM_-_ViewModel_example.php

模板和 ViewModel 一一對應,意味著 View 只能被一個特定的 ViewModel 使用,反過來也一樣。這會讓我進一步思考,也許我們可以將模板和  ViewModel 封裝成一個 View 對象,更有效地將 Controller 和模板以及 ViewModel 解耦,讓它只依賴一個通用的 View  接口;但我還沒有機會實驗這個想法。

到此,關于“如何理解MVC及其變種”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

mvc
AI