成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

軟件架構(gòu)編年史:MVC及其變種

開發(fā) 架構(gòu)
不久以前,我還為一家公司工作過,其核心業(yè)務(wù)應(yīng)用是擁有數(shù)千家公司客戶的 SaaS 平臺。這個至關(guān)重要的應(yīng)用已經(jīng)開發(fā)了三年,代碼文件中混雜著 HTML、CSS、業(yè)務(wù)邏輯和 SQL。果然,在發(fā)布兩年之后,公司決定完全重寫這個應(yīng)用。

[[392479]]

 本文轉(zhuǎn)載自微信公眾號「逸言」,作者覃宇 。轉(zhuǎn)載本文請聯(lián)系逸言公眾號。  

創(chuàng)建可維護(hù)的應(yīng)用始終是構(gòu)建應(yīng)用的真正的長期挑戰(zhàn)。

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

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

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

1979 – Model-View-Controller

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

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

  • 代表業(yè)務(wù)邏輯的 Model (模型);
  • 代表 UI 控件的 View (視圖):按鈕、文本框等等;
  • 在視圖和模型之間居中協(xié)調(diào)的 Controller(控制器),這意味著:
    • 它決定顯示哪些視圖以及哪些數(shù)據(jù);
    • 它將用戶操作(例如點擊按鈕)轉(zhuǎn)換成業(yè)務(wù)邏輯。

模型可以是單個對象(相當(dāng)無趣),也可以是對象組成的某種結(jié)構(gòu)。——Trygve Reenskaug 1979, MVC

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

  • View 直接使用 Model 數(shù)據(jù)對象來展示數(shù)據(jù);
  • 當(dāng) Model 發(fā)生變化時,會觸發(fā)一個事件立即更新 View(記住,1979年還沒有 HTTP);
  • 每一個 View 通常只關(guān)聯(lián)一個 Controller;
  • 每個界面可以包含多對 View 和 Controller;
  • 每個Controller 可以對應(yīng)多個 View。

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

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

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

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

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

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

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

1996 – Model-View-Presenter

MVC 模式給當(dāng)時的編程范式注入了一劑強(qiáng)心針。然而,隨著應(yīng)用程序復(fù)雜度的增加,需要更進(jìn)一步地解耦。

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

  • View 是被動的,對 Model 無感知;
  • 專注于輕量 Controller(Presenter),它們不包含任何業(yè)務(wù)邏輯,只是簡單地調(diào)用命令和/或查詢模型,將原始數(shù)據(jù)傳遞給 View;
  • 數(shù)據(jù)的變化不會直接觸發(fā) View 的更新:它始終要通過 Presenter,由 Presenter 來更新 View。這樣在更新視圖之前 Controller(Presenter) 還可以執(zhí)行一些和展現(xiàn)相關(guān)的額外邏輯。例如,同時更新另一些數(shù)據(jù),它們和數(shù)據(jù)庫中發(fā)生變化的數(shù)據(jù)有關(guān);
  • 每個 View 對應(yīng)一個 Presenter。

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

MVP 中的 Presenter 又被稱為 Supervisor Controller。

2005 – Model-View-ViewModel

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

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

Controller 被 ViewModel “取代”:

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

MVVM 背后的思想是:

  • ViewModel 和 View 一一對應(yīng);
  • 將 View 中的邏輯轉(zhuǎn)移到 ViewModel 來簡化 View;
  • View 使用的數(shù)據(jù)和 ViewModel 中的數(shù)據(jù)一一對應(yīng);
  • 將 ViewModel 中的數(shù)據(jù)綁定到 View 中的數(shù)據(jù)上,這樣 ViewModel 中數(shù)據(jù)的變化會立即體現(xiàn)在 View 上。

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

Model-View-Presenter-ViewModel

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

Model

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

View

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

ViewModel(又叫做 Presentation Model)

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

  • Model 中的變化(比如實體結(jié)構(gòu)的變化)會上升并影響 ViewModel,但不會影響模板;
  • 復(fù)雜的展現(xiàn)邏輯被封裝到了 ViewModel 之中,因此不會被泄露(例如,在業(yè)務(wù)實體中創(chuàng)建一些只和展現(xiàn)邏輯有關(guān)的方法)到領(lǐng)域之中;
  • 模板的依賴變得很清晰,因為它們必須在 ViewModel 中設(shè)置。例如,暴露出依賴可以幫助我們決定應(yīng)該優(yōu)先從數(shù)據(jù)庫中加載哪些內(nèi)容來避免 N+1 問題。

Presenter

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

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

  1. <?php 
  2. // src/UI/Admin/Some/Controller/Namespace/Detail/SomeEntityDetailController.php 
  3. namespace UI\Admin\Some\Controller\Namespace\Detail; 
  4. // use ... 
  5. final class SomeEntityDetailController 
  6.     /** 
  7.      * @var SomeRepositoryInterface 
  8.      */ 
  9.     private $someRepository; 
  10.    
  11.     /** 
  12.      * @var RelatedRepositoryInterface 
  13.      */ 
  14.     private $relatedRepository; 
  15.     /** 
  16.      * @var TemplateEngineInterface 
  17.      */ 
  18.     private $templateEngine; 
  19.     public function __construct( 
  20.         SomeRepositoryInterface $someRepository, 
  21.         RelatedRepositoryInterface $relatedRepository, 
  22.         TemplateEngineInterface $templateEngine 
  23.     ) { 
  24.         $this->someRepository = $someRepository; 
  25.         $this->relatedRepository = $relatedRepository; 
  26.         $this->templateEngine = $templateEngine; 
  27.     } 
  28.     /** 
  29.      * @return mixed 
  30.      */ 
  31.     public function get(int $someEntityId) 
  32.         $mainEntity = $this->someRepository->getById($someEntityId); 
  33.         $relatedEntityList = $this->relatedRepository->getByParentId($someEntityId); 
  34.         return $this->templateEngine->render( 
  35.             '@Some/Controller/Namespace/Detail/details.html.twig'
  36.             new DetailsViewModel($mainEntity, $relatedEntityList) 
  37.         ); 
  38.     } 

M-V-C-VM_-_Controller_example.php

  1. <?php 
  2. // src/UI/Admin/Some/Controller/Namespace/Detail/DetailsViewModel.php 
  3. namespace UI\Admin\Some\Controller\Namespace\Detail; 
  4. // use ... 
  5. final class DetailsViewModel implements TemplateViewModelInterface 
  6.     /** 
  7.      * @var array 
  8.      */ 
  9.     private $mainEntity = []; 
  10.     /** 
  11.      * @var array 
  12.      */ 
  13.     private $relatedEntityList = []; 
  14.     /** 
  15.      * @var bool 
  16.      */ 
  17.     private $shouldDisplayFancyDialog = false
  18.     /** 
  19.      * @var bool 
  20.      */ 
  21.     private $canEditData = false
  22.     /** 
  23.      * @param SomeEntity $mainEntity 
  24.      * @param RelatedEntity[] $relatedEntityList 
  25.      */ 
  26.     public function __construct(SomeEntity $mainEntity, array $relatedEntityList) 
  27.         $this->mainEntity = [ 
  28.             'name' => $mainEntity->getName(), 
  29.             'description' => $mainEntity->getResume(), 
  30.         ]; 
  31.         foreach ($relatedEntityList as $relatedEntity) { 
  32.             $this->relatedEntityList[] = [ 
  33.                 'title' => $relatedEntity->getTitle(), 
  34.                 'subtitle' => $relatedEntity->getSubtitle(), 
  35.             ]; 
  36.         } 
  37.          
  38.         $this->shouldDisplayFancyDialog = /* ... some complex conditional using the entities data ... */ ; 
  39.          
  40.         $this->canEditData = /* ... another complex conditional using the entities data ... */ ; 
  41.     } 
  42.     public function getMainEntity(): array 
  43.         return $this->mainEntity; 
  44.     } 
  45.     public function getRelatedEntityList(): array 
  46.         return $this->relatedEntityList; 
  47.     } 
  48.     public function shouldDisplayFancyDialog(): bool 
  49.         return $this->shouldDisplayFancyDialog; 
  50.     } 
  51.     public function canEditData(): bool 
  52.         return $this->canEditData; 
  53.     } 

M-V-C-VM_-_ViewModel_example.php

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

總結(jié)

在網(wǎng)上,我們還能找到其它 MVC 的變種。但是,這里列出是我覺得更有意義和/或與我的工作有關(guān)的一些模式。

然而,我在本文中引用的這些模式是為桌面應(yīng)用程序和/或富客戶端的上下文創(chuàng)建的,因此它們不是總能和請求/響應(yīng)范式百分之百的匹配。

如果你開發(fā)的是云原生的企業(yè)應(yīng)用并且使用了 MVC,實際上你多半使用的是更接近 MVP 的某種模式。但無論如何,我想表達(dá)的不是應(yīng)該尊崇某種特定的 MVC 變種或是刻板地理解它們的名字,而是我們應(yīng)該學(xué)習(xí)所有的模式,按照需要去使用和調(diào)整它們。還是那句老話,最終目標(biāo)就是高內(nèi)聚低耦合:關(guān)注點分離。

 引用來源

  • 1979 – Trygve Reenskaug – MVC XEROX PARC 1978-79
  • 1979 – Trygve Reenskaug – MVC
  • 1987 – Joelle Coutaz – PAC, an Object Oriented Model for Dialog Design
  • 1996 – Mike Potel – MVP: Model-View-Presenter: The Taligent Programming Model for C++ and Java
  • 2000 – Jason Cai, Ranjit Kapila, Gaurav Pal – HMVC: The layered pattern for developing strong client tiers
  • 2003 -Trygve Reenskaug – The Model-View-Controller (MVC): Its Past and Present
  • 2004 -Martin Fowler – Presentation Model
  • 2005 – John Gossman – Introduction to Model/View/ViewModel pattern for building WPF apps
  • 2006 – Martin Fowler – Supervising Controller
  • 2006 – Martin Fowler – GUI Architectures
  • 2011 – Mārti?? Tere?ko – Architecture more suitable for web apps than MVC?
  • 2017* – Tracy-Gregory J. Gilmore – Never the twain shall meet. The tale of MV*
  • 2017* – Tech notes – MVVM vs MVP vs MVC: The differences explained
  • 2017* – Wikipedia – Model–view–controller
  • 2017* – Wikipedia – Presentation–abstraction–control
  • 2017* – Wikipedia – Model-view-presenter
  • 2017* – Wikipedia – Hierarchical model–view–controller
  • 2017* – Wikipedia – Model–view–viewmodel
  • 2018* – Wikipedia – History of the graphical user interface

覃宇,Android開發(fā)者/ThoughtWorks技術(shù)教練//譯者,熱衷于探究軟件開發(fā)的方方面面,從端到云,從工具到實踐。喜歡通過翻譯來學(xué)習(xí)和分享知識,譯作有《Kotlin實戰(zhàn)》、《領(lǐng)域驅(qū)動設(shè)計精粹》、《Serverless架構(gòu):無服務(wù)器應(yīng)用與AWS Lambda》和《云原生安全與DevOps保障》。

[[392480]]

 

責(zé)任編輯:武曉燕 來源: 逸言
相關(guān)推薦

2021-03-26 07:47:18

單體架構(gòu)程序

2021-06-09 08:09:05

架構(gòu)軟件整潔

2012-06-11 15:29:14

OpenStack

2021-04-30 09:16:08

軟件架構(gòu)命名

2015-07-17 13:27:06

2011-07-20 09:19:47

SQL Server

2019-01-23 17:37:58

華為

2012-12-21 15:32:32

Linux Deepi

2014-12-12 14:55:42

移動惡意代碼移動安全

2014-10-29 14:43:05

2014-05-20 17:26:06

2012-03-30 11:34:52

Linux Deepi

2013-10-31 14:31:09

2011-08-08 09:25:55

2015-12-16 10:11:14

2020-08-21 13:54:24

瀏覽器程序員內(nèi)核

2023-09-06 06:46:40

顯卡接口RTX 4060

2025-01-23 09:07:51

2009-09-22 08:14:23

2014-05-09 11:19:38

iOS移動互聯(lián)網(wǎng)
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 人人射人人插 | 欧美午夜精品 | 欧美一区二区三 | 一区二区三区成人 | 色综合视频| 日韩精品1区2区 | 欧美精品一区二区免费 | 天天躁日日躁aaaa视频 | 九九久久这里只有精品 | 久热精品在线播放 | 欧美日韩不卡在线 | 久久国产一区二区三区 | 91国内视频在线 | 男人的天堂在线视频 | 日日日日日日bbbbb视频 | 久久伊人免费视频 | 国产a视频| 青青草一区二区三区 | 亚洲一区二区久久 | 中文字幕免费在线 | 国产中文字幕亚洲 | 日韩精品视频在线免费观看 | 欧美精品久久久 | 午夜影院网站 | 欧美一级高潮片免费的 | 国产伦精品一区二区三区视频金莲 | 欧美成年人视频在线观看 | 免费看一级毛片 | 精品欧美一区二区中文字幕视频 | 久久亚洲天堂 | 国产成人a亚洲精品 | 亚洲欧美在线视频 | 精品国产18久久久久久二百 | 国产精品不卡视频 | 91精品国产色综合久久 | 色综久久 | 欧美精品在线观看 | 国产在线视频99 | 99久久免费精品国产男女高不卡 | 狼人伊人影院 | 国产电影一区二区三区爱妃记 |