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

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

yuyutoo 2024-12-08 19:47 3 浏览 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* – 维基百科 –模型–视图–视图模型

相关推荐

TCP协议原理,有这一篇就够了

先亮出这篇文章的思维导图:TCP作为传输层的协议,是一个软件工程师素养的体现,也是面试中经常被问到的知识点。在此,我将TCP核心的一些问题梳理了一下,希望能帮到各位。001.能不能说一说TC...

Win10专业版无线网络老是掉线的问题

有一位电脑基地的用户,使用...

学习计算机网络需要掌握以下几方面基础知识

计算机基础知识操作系统:了解常见操作系统(如Windows、Linux)的基本操作和网络配置,例如如何设置IP地址、子网掩码、网关和DNS服务器等,以及如何通过命令行工具(如ping、tr...

网络工程师的圣经!世界级网工手绘268张图让TCP/IP直接通俗易懂

要把知识通俗地讲明白,真的不容易。——读者说TCP/IP从字面意义上讲,有人可能会认为TCP/IP是指TCP和IP两种协议。实际生活当中有时候也确实就是这两种协议。然而在很多情况下,它只是...

三分钟了解通信知识TCP与IP协议(含“通信技术”资料分享)

TCP/IPTCP/IP分层模型①应用层...

网闸与防火墙:网络安全设备的差异与应用

在网络安全领域,网闸(安全隔离网闸,GAP)和防火墙(Firewall)是两类重要的防护设备。尽管它们都服务于网络安全防护,但在设计理念、技术原理、安全效能及适用场景等方面存在显著差异,以下从五个维度...

S7-300的TCP/IP通信

一、首先在项目中创建2个S7-300的站点;二、硬件组态中,设置合适的TCP/IP地址,在同一网段内;...

西门子S7-1500 PLC的 MODBUS TCP通信

MODBUSTCP使MODBUS_RTU协议运行于以太网,MODBUSTCP使用TCP/IP和以太网在站点间传送MODBUS报文,MODBUSTCP结合了以太网物理网络和网络标准TC...

系统规划与管理师新版备考必备:第7章考点思维导图解析

备考系统规划与管理师的小伙伴们,福利又来啦!今天为大家带来《系统规划与管理师(第2版)》第7章考点的思维导图,助你高效梳理重点,让备考更有方向!...

TCP/IP、Http、Socket 有何区别与联系?

HTTP协议对应于应用层,Socket则是对TCP/IP协议的封装和应用(程序员层面上)。HTTP是应用层协议,主要解决如何包装数据。而我们平时说的最多的Socket是什么呢?实际上...

西门子PLC串口协议与以太网通信协议对比

西门子plc品牌众多,通信协议的类型就更多了,具体可分为串口协议和以太网通信协议两大类。...

网络编程懒人入门(十三):一泡尿的时间,快速搞懂TCP和UDP的区别

本文引用了作者Fundebug的“一文搞懂TCP与UDP的区别”一文的内容,感谢无私分享。1、引言...

程序员必备的学习笔记《TCP/IP详解(一)》

为什么会有TCP/IP协议在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别。就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样...

一文读懂TCP/IP协议工作原理和工作流程

简述本文主要介绍TCP/IP协议工作原理和工作流程。含义TCP/IP协议,英文全称TransmissionControlProtocol/InternetProtocol,包含了一系列构成互联网...

如何在 Windows 10 和 Windows 11 上重置 TCP/IP 堆栈

传输控制协议/Internet协议,通常称为TCP/IP,是您的WindowsPC如何与Internet上的其他设备进行通信的关键部分。但是当事情出错时会发生什么?你如何解决它?幸运的...

取消回复欢迎 发表评论: