软件架构(7)-MVC MVP MVVM你必须知道的事
yuyutoo 2024-12-08 19:47 5 浏览 0 评论
这篇文章是软件架构编年史的一部分,这是一系列关于软件架构的文章。在其中,我写下了我对软件架构的了解、我对它的看法以及我如何使用这些知识。如果您阅读了本系列的前几篇文章,这篇文章的内容可能会更有意义。
创建可维护的应用程序一直是构建应用程序的真正长期挑战。
不久前,我在一家公司工作,该公司的核心业务应用程序是 SaaS 平台,被数千家客户公司使用。那个关键的应用程序已经使用了三年,代码文件中混合了 HTML、CSS、业务逻辑和 SQL。当然,发布两年后,公司决定从头开始重建它。虽然这些情况仍然存在,但今天我们中的许多人都知道这些做法是错误的,并且知道如何避免它们。
然而,回到 70 年代,混合职责是常见的做法,人们仍在努力探索如何更好地做到这一点。随着应用程序复杂性的增加,对 UI 的更改将不可避免地意味着对业务逻辑的更改,从而增加了更改的复杂性、进行这些更改所花费的时间以及出现错误的可能性(因为会更改更多代码)。
MCV 通过促进前端和后端之间的“关注点分离”来解决这些问题。
1979 – 模型-视图-控制器
为了解决上述问题,1979 年,Trygve Reenskaug 提出了 MVC 模式作为分离关注点的方法,将 UI 与业务逻辑隔离开来。该模式用于自 1973 年以来就存在的桌面 GUI环境。
MVC 模式将代码分为三个概念单元:
- 模型代表业务逻辑;
- View代表UI中的一个widget:按钮、文本框等;
- 控制器提供视图和模型之间的协调。这意味着:决定显示哪些视图,以及显示哪些数据;将用户操作(即单击按钮)转换为业务逻辑。
模型可以是单个对象(相当无趣),也可以是对象的某种结构。
Trygve Reenskaug 1979,MVC
了解原始 MVC 模式的其他重要概念是:
- 视图直接使用模型数据对象来显示它们的数据;
- 当模型数据发生变化时,它会触发一个事件,该事件会立即更新 View(请记住,1979 年还没有 HTTP);
- 通常,每个视图都关联到一个控制器;
- 每个屏幕可以包含几对视图和控制器;
- 每个控制器可能有多个视图。
我熟悉的今天的 HTTP 请求和响应范例并没有使用这种原始的 MVC 风格,因为在这种情况下,流程从 View 到 Controller,就像我熟悉的那样,但随后是另一个方向它直接从模型流向视图,而不通过控制器。
此外,在当前的请求和响应范例中,当数据库中的数据发生变化时,它不会触发浏览器中显示的视图中的更新(尽管这可以使用网络套接字实现)。要查看更新后的数据,用户需要发出新的请求,更新后的数据将始终通过控制器返回。
1987/2000 – PAC / 分层模型-视图-控制器
PAC,又名 HMVC,在UI 部分的小部件化上下文中提供了增强的模块化。
例如,当我们有一个视图,其中的一个部分在其他几个视图中以完全相同的格式使用,甚至只是在同一个视图中重复使用。一个实际示例是包含 RSS 提要内容的网页部分,该部分在其他页面中重复使用。
使用 HMVC,处理主请求的控制器会将子请求转发给其他控制器,以获取小部件的渲染,然后将其合并到主视图的渲染中。
就个人而言,我在 HTTP 请求/响应范例的上下文中遇到过几次这种需求,但我发现让 UI 对可以呈现小部件的控制器进行 AJAX 调用是一种更简单的方法。这保持了模块化的好处,而没有增加嵌套控制器调用的复杂性,而且这些子请求可以缓存在 Varnish 之类的东西中。
1996 年– 模型视图演示者
MCV 模式极大地改进了当时的编程范式。然而,随着应用程序复杂性的增加,进一步解耦的需求也随之增加。
1996 年,IBM 子公司 Taligent 公开了他们基于 MVC 的 MVP 模式。这个想法是进一步将模型与 UI 问题隔离开来:
- 视图是被动的,不知道模型;
- 专注于不包含业务逻辑并仅调用模型中的命令和/或查询,将原始数据传递给视图的瘦控制器(呈现器);
- 数据的变化不会直接触发视图的更新:它总是通过展示者,展示者又更新视图。这允许控制器(呈现器)在更新视图之前执行额外的与呈现相关的逻辑。例如,还更新与数据库中更改的数据相关的数据;
- 每个视图都有一个演示者。
这更接近于我在今天的请求/响应范例中所看到的:流程总是经过 Controller/Presenter。尽管如此,Presenter 仍然不会主动更新视图,它总是需要执行新的请求才能使更改可见。
在 MVP 中, Presenter 也称为Supervisor Controller。
2005 – 模型-视图-视图模型
再次,源于应用程序复杂性的增加,2005 年,微软的 WPF 和 Silverlight 架构师之一约翰·戈斯曼 (John Gossman) 宣布了 MVVM 模式,其目标是进一步将 UI 设计与代码分离,并提供从视图到数据模型的数据绑定.
[MVVM] 是 [MVC] 的变体,专为现代 UI 开发平台量身定制,其中 View 由设计师负责,而不是传统开发人员。[...] 应用程序的 UI 部分正在使用不同的工具、语言和由不同的人开发,而不是业务逻辑或数据后端。
Controller 被 ViewModel “替换”了:
[视图] 对键盘快捷键进行编码,控件本身管理与输入设备的交互,这是 MVC 中 Controller 的责任(现代 GUI 开发中 Controller 到底发生了什么是一个很长的题外话……我倾向于认为它只是逐渐消失了背景。它仍然存在,但我们不必像 1979 年那样考虑太多)。
John Gossman 2005,模型/视图/视图模型模式简介
MVVM 背后的想法是:
- 一个 ViewModel 只对应一个 View,反之亦然;
- 将视图逻辑移至 ViewModel 以简化视图;
- 视图中使用的数据与ViewModel中的数据之间的一对一映射;
- 将 ViewModel 中的数据绑定到 View 中的数据,以便当 ViewModel 中的数据发生更改时,它会立即反映在 View 中。
就像原始 MVC 模式的情况一样,这种方法在传统的请求/响应范例中是不可能的,因为 ViewModel 不能主动更新视图(除非使用 Web 套接字)并且 MVVM 需要它。此外,根据我的经验,ViewModel 具有与 View 中使用的数据匹配的属性这一事实并不是 Controller 中的常见做法。
模型视图演示者视图模型
在为云构建复杂的企业应用程序时,我更喜欢将应用程序 UI 结构合理化为 MVP-VM,其中 ViewModel 是 Martin Fowler在 2004 年称之为Presentation Model的东西。
- 模型
- 一组包含所有业务逻辑和用例的类;
- 看法
- 模板,使用模板引擎生成 HTML;
- ViewModel(又名演示模型)
- 从查询(或从中提取原始数据的模型实体)接收原始数据,并保存要在模板中使用的数据。它还封装了复杂的表示逻辑,以简化模板。我发现 ViewModel 的使用特别重要,因为我们不会被诱惑在模板中使用实体,这使我们能够将视图与模型完全隔离:
- 模型中的更改(即实体结构中的更改)可能会冒泡并影响 ViewModel,但不会影响模板;
- 复杂的表现逻辑不会泄漏到域中(即在业务实体中创建与表现逻辑完全相关的方法),因为我们可以将其封装在 ViewModel 中;
- 模板的依赖关系变得明确,因为它们必须在 ViewModel 中设置。使这些依赖关系可见可以帮助我们,例如,决定应该从数据库中急切加载什么以防止 N+1 问题。
- Presenter
- 接收 HTTP 请求,触发命令或查询,使用查询返回的数据、ViewModel、Template 和模板引擎生成 HTML 并将其发送回客户端。所有 Views 交互都通过 Presenter。
这是我如何做的一个简单(和天真的)代码示例:
<?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)
);
}
}
<?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;
}
}
模板和 ViewModel 具有一对一的匹配关系,这意味着一个 View 只能与特定的 ViewModel 一起使用,反之亦然。这实际上什至让我觉得也许 我们可以将模板和 ViewModel 封装在一个 View 对象中,有效地将 Controller 与模板和 ViewModel 解耦,使其依赖于一个通用的 View Interface,但我从未尝试过这个。
结论
我们可能会在网络上找到 MVC 的其他变体。然而,这些是我觉得更有趣和/或与我的工作相关的。
尽管如此,我在这里引用的模式是为桌面应用程序和/或富客户端的上下文创建的,因此它们并不总是 100% 适合请求/响应范例。
如果你正在做企业云应用程序并且你正在使用 MVC,那么很可能你实际上使用的是更接近 MVP 的东西,但无论如何,我的意思不是要遵循 MVC 的特定变体或知道所有名称并严格要求它,我的观点是我们应该向所有这些人学习,这样我们就可以根据需要使用和适应。像往常一样,最终目标是低耦合和高内聚:关注点分离。
参考资料:https://herbertograca.com/2017/08/17/mvc-and-its-variants/
2017* – 维基百科 –模型–视图–视图模型
相关推荐
- 墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人
-
中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...
- 网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕
-
伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...
- 一课译词:来龙去脉(来龙去脉 的意思解释)
-
Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...
- 高考重要考点:range(range高考用法)
-
range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...
- C++20 Ranges:现代范围操作(现代c++白皮书)
-
1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...
- 学习VBA,报表做到飞 第二章 数组 2.4 Filter函数
-
第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...
- VBA学习笔记:数组:数组相关函数—Split,Join
-
Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...
- VBA如何自定义序列,学会这些方法,让你工作更轻松
-
No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...
- Excel VBA入门教程1.3 数组基础(vba数组详解)
-
1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...
- 远程网络调试工具百宝箱-MobaXterm
-
MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...
- AREX:携程新一代自动化回归测试工具的设计与实现
-
一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...
- Windows、Android、IOS、Web自动化工具选择策略
-
Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...
- python自动化工具:pywinauto(python快速上手 自动化)
-
简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...
- 时下最火的 Airtest 如何测试手机 APP?
-
引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...
- 【推荐】7个最强Appium替代工具,移动App自动化测试必备!
-
在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)