百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

软件架构(7)-MVC MVP MVVM你必须知道的事

yuyutoo 2024-12-08 19:47 2 浏览 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 模式的其他重要概念是:

  1. 视图直接使用模型数据对象来显示它们的数据;
  2. 当模型数据发生变化时,它会触发一个事件,该事件会立即更新 View(请记住,1979 年还没有 HTTP);
  3. 通常,每个视图都关联到一个控制器;
  4. 每个屏幕可以包含几对视图和控制器;
  5. 每个控制器可能有多个视图。

我熟悉的今天的 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* – 维基百科 –模型–视图–视图模型

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: