MVC、MVP、MVVM 解析【理论篇】

前言,在游戏开发中,经常会听到MVC、MVP、MVVM 这类名词,对他们的第一印象多是为了解耦、提高扩展性而选择的软件架构模式,但分别深究它们的区别或优缺点,又往往不得要领,今天笔者就基于Unity游戏开发分别对这三种软件架构模式进行讲解。

一、MVC

1.1 历史背景
MVC模式最早由Trygve Reenskaug在1978年提出[1],是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件架构。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式透过对复杂度的简化,使程序结构更加直观。软件系统透过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。

1.2 MVC的目的
Unity游戏开发中使用MVC模式主要是为了应对日益复杂的业务需求,不管是从学习成本、健壮性[1]和多人并行开发来考虑,都是非常适合的。所以MVC模式也是在游戏开发中(特别是外围系统)使用非常广泛的一种架构模式。

1.3 MVC的架构模式

  • Model - 数据层,负责处理数据相关的操作。这部分多数只管能否做,而不考略是否应该做。
  • View - 视图层,用户的界面。例如:对界面组件的持有、不含业务逻辑的开关等操作。
  • Controller- 控制层,业务逻辑所在的层级。这部分是判断是否应该做、如何做的地方。

1.4 MVC的架构示例图
它们的依赖关系或数据的走向常规有以下3种,而第三种也是使用最频繁的一种(其实项目中多数是使用第三种的变种)

1.5 示例图解析

示例图【一】
View层接收到用户的输入,将接收到数据信息推送到Controller层,Controller层收到View的数据,进行下一步的解析和整合,将整合好的数据在推送到Model层,Model层接收到Contoller层整合后的数据,如果需要更新View层(界面),就会向View发送消息通知更新界面。

示例图【二】
与示视图【一】最大的区别就是数据的流向都是是双向的。基于第一张图的说明补充为什么是双向的。

  • View层为什么会直接到Model层?
    因为有一些界面产生的数据是不需要Controller层进行解析或整合,可以直接被Model层所使用。例如基于分析用户行为的打点,其实只是向数据库发送是否点击,没有其他的业务逻辑,所以理论上可以直接发送到Model层。
  • Controller层为什么会直接到View层?
    因为一些操作不涉及Model层,仅仅是表现上的差异,所以Controller层就可以直接操作View层来控制表现上的差异。例如:不需要同步配置的装饰性UI,圣诞节雪花、万圣节偶尔飞过的蝙蝠等等。
  • Model层为什么会直接到Controller层?
    因为Model层所发出的所有数据并不是都可以被View直接使用的,需要二次的解析或整合成可用的数据,然后再由Controller层推送的View层。例如:Model层下发一个用户设置的数据结构,里面有声音、画质、各种偏好等等都在一个数据结构中,这个时候就需要发送到Controller层进行解析,然后根据解析的内容分别推送到View层。

示例图【三】
与示例图【二】的区别是断开的了View层到Model层方向的连接,这么做的主要目的是提高各个层级的内聚性,尤其是提高Controller层的内聚性,更好的控制数据的流向与业务逻辑的管理。

对于MVC模式的运用没有绝对的标准,在WPF、Web、IOS、Android、Unity开发都有各自演变,其不变得主旨是对于 数据-视图-控制 三方面的解耦,提高各自的内聚性,从而达到进一步提高健壮性[1] 的效果。

1.6 MVC的优缺点

优点

  • 分为职责更清晰的Model、View、Controller三层,更符合高内聚低耦合的原则。
  • 利于需求的变动,维护成本降低。

缺点

  • Model层和View的联系增加了数据流向的选择,其结果就是造成维护数据流向成本的提高,更容易出错。*
  • Controller层会随着View复杂度的增加而变得异常庞大,所以需要拆分为多个子Controller。


二、MVP

2.1 历史背景
Model-view-presenter,简称MVP,是电脑软件设计工程中一种对针对MVC模式,再审议后所延伸提出的一种软件设计模式。
Model-View-Presenter (MVP)用户界面设计模式的一种,被广泛用于便捷自动化单元测试和在呈现逻辑中改良分离关注点(separation of concerns)。

2.2 MVP的目的
基于MVC的普及性和成功使用的案例,进一步优化而得到的产物。在原有的MVC模式的基础上切断了View层和Model层的联系,着重关注Presenter/Controller层,也进一步提高了Presenter/Controller层的控制权限。

2.3 MVP的架构模式

  • Model 定义用户界面所需要被显示的资料模型,一个模型包含着相关的业务逻辑(在实际开发中,数据相关的业务逻辑都会放到服务器)。
  • View 视图为呈现用户界面的终端,用以表现来自 Model 的资料,和用户命令路由再经过 Presenter 对事件处理后的资料。
  • Presenter 包含着组件的事件处理,负责检索 Model 获取资料,和将获取的资料经过格式转换与 View 进行沟通。

2.4 MVP的架构示例图
它们的依赖关系或数据的走向常规有以下2种,这两种也是在实际开发中使用的方式。
MVP与MVC最大的区别是切断了View层和Model层的联系,View和Model上的变动全部经由Presenter层处理和中转。

2.5 示例图解析

示例图【一】
这种方式可以算是在开发中使用频率最高的了(因为lua的缘故),在开发中通常会在对应的Prefab上添加对应的UINodeReference[2]组件来收集对应的节点或者组件,然后通过Presenter层持有UINodeReference组件来达到控制的目的。View层产生的数据都会经过Presenter层处理并推送到Model层,Model反之如此。这样对于数据的流向、控制更集中,提高了View的利用率。例如View中的界面重新设计,只要UINodeReference组件中的节点不变,View层的替换不会影响Presenter层的使用。

示例图【二】
这种方式是笔者最为推荐的,但是基于实际开发中使用Lua热更的特点(语法和编辑器都不如C#友好),并不能得到很好的应用。此方式将View层再次拆分为View层和Controller层,View层和Controller层依靠Target–Action模式进行关联。此模式笔者在Unity 之 经典优秀框架 PureMVC(4.1.0版本)解析中对于Panel和Mediator的关联有过应用。它最大的特点是将纯UI和对应的UI事件进行再次分离达到解耦的目的。

2.6 MVP的优缺点

优点

  • 基于MVC的改良,含有MVC的优点。
  • 降低Model层和View的联系,使数据流向更可控。

缺点

  • Presenter/Controller层的负担再次加重,会随着View复杂度的增加比MVC变的更加庞大,需要拆分为多个子Presenter。


三、MVVM

3.1 历史背景
MVVMModel–view–viewmodel)是一种软件架构模式
MVVM是马丁·福勒的PM(Presentation Model)设计模式的变体。[2][3] MVVM以相同的方式抽象出视图的状态和行为,[3] 但PM以依赖于特定用户界面平台的方式抽象出视图(创建了视图模型)。MVVM和PM都来自MVC模式。
MVVM由微软架构师Ken Cooper和Ted Peters开发,通过利用WPF(微软.NET图形系统)和Silverlight(WPF的互联网应用派生品)的特性来简化用户界面的事件驱动程序设计[3] 微软的WPF和Silverlight架构师之一John Gossman于2005年在他的博客上发表了MVVM。
MVVM也被称为model-view-binder,特别是在不涉及.NET平台的实现中。ZK(Java写的一个Web应用框架)和KnockoutJS(一个JavaScript)使用model-view-binder。[3][4][5]

3.2 MVVM的目的
将原来的Presenter/Controller替换为现在ViewModel,更改了原来的数据更新方式,将View和ViewModel进行双向绑定(data-binding),当View中指定的数据更改时自动更新到View层,反之View层中的数据有变动,也会自动推送到ViewModel层,MVVM与MVC/MVP最大的改变就是数据变更后自动触发更新事件。

3.3 MVVM的架构模式

  • Model 定义用户界面所需要被显示的资料模型,一个模型包含着相关的业务逻辑(在实际开发中,数据相关的业务逻辑都会放到服务器)。
  • View 视图为呈现用户界面的终端,用以表现来自 Model 的资料,和用户命令路由再经过 Presenter 对事件处理后的资料。
  • ViewModel 在原有Controller层的基础上,将业务逻辑封和组件进行双向绑定(data-binding),达到同步更新的目的。

3.4 MVVM的架构示例图

3.5 解析
MVVM模式在实际开发中应用的范围不多,在笔者的过往经历中,以MVVM模式为主力的开发几乎没有,不仅仅是学习和维护的成本,而且在以Lua热更为主流的今天,采用双向绑定这种策略对于lua来说更是难上加难,例如下面一个简单的泛型绑定类BindableProperty,如果用lua来达到同样简单的实现效果,本身就是一个难题。
对这种模式也有来自MVVM的创造者John Gossman本人的批评,[13]他指出,实现MVVM的开销对于简单的UI操作是“过度的”。他说,对于更大的应用来说,推广ViewModel变得更加困难。而且,他说明了非常大的应用程序中的数据绑定会导致相当大的内存消耗。

public class BindableProperty<T>
    {
        public Action<T, T> OnValueChanged;
        private T _value;
        public T Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (!Equals(_value, value))
                {
                    T old = _value;
                    _value = value;
                    ValueChanged(old, _value);
                }
            }
        }

        private void ValueChanged(T oldValue, T newValue)
        {
            OnValueChanged?.Invoke(oldValue, newValue);
        }

        public override string ToString()
        {
            return (Value != null ? Value.ToString() : "null");
        }
    }

3.6 MVVM的优缺点

优点

  • 基于MVP的改良,含有MVP的优点
  • 增加了双向绑定(data-binding)等操作,使数据推送/更新等操作更自动化。

缺点

  • ViewModel层的膨胀的问题依然没有得到解决,只能拆分为多个子ViewModel。
  • 非常依赖语言的特性和使用的环境(WPF),对一些动态语言如Lua不是很友好。

总结

随着界面复杂度的增加,架构的不同变种也在应运而生,从MVC--MVP--MVVM的发展我们可以看出,都是基于现有使用环境的情况下,在原来的优点上进行优化,所以说对于MVC/MVP/MVVM三者并没有绝对银弹。希望这篇文章能够帮到你或者给予一些启示,找到适合自己的MV*,找到自己的银弹。

有补充或者更好的想法欢迎给我留言。
如发现不正确或者不准确的地方化欢迎留言指正。


参考链接:


  1. 健壮性(鲁棒性)和可靠性是有区别的,两者对应的英文单词分别是robustness 和 reliability。健壮性主要描述一个系统对于参数变化的不敏感性,而可靠性主要描述一个系统的正确性,也就是在你固定提供一个参数时,他应该产生稳定性的、能预测的输出。例如一个程序,他的设计目标是获取一个参数并输出一个值。假如他能正确完成这个目标,就说他是可靠的。但在这个程序执行完毕后,假如没有正确的释放内存,或者说系统没有自动帮它释放占用的资源,就认为这个程序及其“运行时”不具健壮性或者鲁棒性。【CLR Via C#】 P357

  2. 泛指挂在Prefab上的Mono脚本,此脚本的public 变量为需要业务逻辑控制的UI节点。