MVC介绍
MVC在WEB开发中用于界定工作属于Server端还是WEB前端还是十分清晰的。这也就是大家目前常见的MVC设计模式:
View<===>Controller<===>Model
即Controller作为粘合剂分别于View和Model进行双向通讯。
我也一度认为这就是经典MVC设计模式,虽然我一直知道MVC设计模式的起源是开发客户端应用。但当我在做一个iOS移动端应用架构设计时发现,如果沿用Model到View的通讯必须借助Controller,那么一些数据变更引发View显示变更的场景的实现是十分困难的。于是我增加了一种Model===>View单向通讯的方式,借助一种特殊用途的对象:ModelView。稍后我会说明ModelView和MVVM中ViewModel的相似与不同。
在写这篇文章前,我再次翻阅MVC设计模式的介绍时发现,原来WEB-MVC是经典MVC的变形,也就是说,在经典MVC中,Model的设计是可以向View进行单向通讯的,原文如下:
- A Domain element was known as a Model and was ignorant of the user-interface (Views and Controllers)
- Presentation was taken care of by the View and the Controller, but there wasn’t just a single View and Controller. A View-Controller pair was required for each element being displayed on the screen and so there was no true separation between them
- The Controller’s role in this pair was handling user input (such as key-presses and click events) and doing something sensible with them
- The Observer pattern was used to update the View whenever the Model changed
原文参见:Developing Backbone.js Applications:Smalltalk-80 MVC
- Model(模型)对象与UI层(Views和Controllers)对象解耦
- 显示控制由VIew和Controller共同负责,但他们并不是独立存在的,对每一个显示元素来说,View对象和Controller对象是成对使用的,他们之间并没有真正意义的解耦。
- Controller在这个组合中的角色是处理用户输入(例如按键和点击事件),并对这些操作做出符合业务需求的处理。
- 使用观察者模式来将Model数据的变更通知View(层,即View+Controller)进行显示更新。
因此我发现,我设计和使用的MMVVC设计方法其实就是经典MVC设计模式,只是对View、Controller、Model的概念又做了更细的分解,以便更好的实现模块复用,以及实现View、Controller、Model三层的开发解耦。
然后再看一段关于Smalltalk-80 MVC的设计意图:
The idea was that decoupling these parts of the application would also allow the reuse of Models for other interfaces in the application.
也就是说,MVC中的Model是希望可以被任何需要展示这部分数据的UI所复用的。为什么会有这个说明呢,是因为在那个年代,出现了这样一种设计模式,原文说明如下:
An approach known as Separated Presentation began to be used as a means to make a clear division between domain objects which modeled concepts in the real world (e.g., a photo, a person) and the presentation objects which were rendered to the user’s screen.
文中提到了两类Object:
Domain Object:也就是MVC中的Model
Presentation Object:用于表示显示数据的对象
MVC的一个设计意图就是通过复用Domain Object,而不是再增加一个叫做Presentation Object的概念。
如果你熟悉MVVM中的ViewModel,你是不是会觉得,ViewModel就是一种Presentation Object?而MVC则是希望通过复用Domain Object来消除Presentation Object。
MMVVC方法以及Objective-C实现
我之所以称MMVVC为一种方法,是因为其本质就是Smalltalk-80 MVC设计模式/架构。只是在MVC设计模式的Model、View、Controller分层上又做了进一步的分层,以便进一步实现View模块的复用,以及实现Model、View、Controller三层的开发解耦(即一个客户端功能的开发可以由三个开发人员同时分别开发View层,Model层、Controller层)。
在MVC中已经很清晰的描述了,Model层是与其他两层解耦的,因此在MVC中Model层是可以独立开发的。但对View和Controller的定义比较模糊,并强调View和Controller是成对出现的,他们直接并没有真正的界限。这就让开发者对什么属于View,什么属于Controller有了自由的理解,从而加剧了View和Controller中业务逻辑出现位置的混乱。
因此我在View和Controller之间,增加了一层ModelView,用来更加清晰明确的界定View和Controller,以及相关业务逻辑应属于哪层:
- Model:领域模型,用于处理和数据相关的操作或业务逻辑,如对数据的增删改查,与UI层(View和Controller)解耦。
- Domain-value Object:用于UI层显示的数据项,隶属Model层(即在进行数据查询等操作时由Model层创建),对UI层仅暴露get方法(通过protocol实现),可通过观察者模式(通过KVO实现)通知UI层数据发生了变更。
- Controller:处理当前页面中的用户交互事件(交互逻辑是什么),基本等同UIViewController。即Controller负责UI层交互相关的业务逻辑。
- ModelView:用于特定数据的展示(应该如何显示)。包括数据处理逻辑(如当name为空时显示字符串:未设置),和显示样式逻辑(如用户头像显示时采用圆形剪裁或圆角剪裁),并对DO进行监听,在DO属性发生变化时更新View。与Controller层解耦,通过观察者模式与Domain-value Object耦合,可以与Model层耦合,用于显示数据的获取,但不应用于数据修改的提交(即处理用户交互事件的结果)。
- View:和业务逻辑完全无关的可复用的View组件,如UIImageView,与Model和Controller解耦。
通过上述分层和ModelView的引入,MMVVC方法对UI层相关业务逻辑的所属位置进行了更为明确的定义,将UI层相关业务逻辑分别划分到了Controller和ModelView,这样,不同的开发人员就可以分别编写一个功能所需的Model层业务逻辑、UI层业务逻辑、特殊需求的可复用View组件。
同时,通过对Model层以及View模块进行合理的设计,甚至在开发一个新功能时,仅需要编写UI层业务逻辑代码(Controller和ModelView)。
ModelView与ViewController
在MVC设计模式的观点中提到,View和Controller一定是成对出现的,Controller负责处理UI业务逻辑中的用户事件,那显然,UI业务逻辑中其余的业务逻辑(如ViewModel中定义的数据转换逻辑)应隶属于View。个人猜测在Smalltalk-80系统下,显示逻辑的复杂程度应该是远远小于当今的,因此即便View的显示逻辑归属于View也不会有问题,然而在当前View日益复杂的今天,特别是View组件的广泛使用,于是大量开发者开始将View显示逻辑放在Controller中。这就是ModelView的作用:处理用户显示逻辑,将不属于Controller中的显示逻辑重新放回View层。
ViewController中的view属性则可视为为一个ModelView。但在显示组件使用方便的今天,为每个ViewController编写一个ModelView类可能并不是一个好的选择,因此,我们可以在loadView中,通过代码,或Storyboard来组装出一个ModelView。仅在一个View需要对数据进行复杂处理逻辑时,或有复杂显示逻辑需要处理时,编写特定的ModelView类,并通过Controller将DO对象设置给这个ModelView。
同时,ModelView可以与Model层耦合,以获取数据,即原Controller中负责数据获取的业务逻辑也应放在ModelView中,但ModelView不应处理用户输入(如对页面数据修改后通知Model修改),这部分逻辑是Controller的职责。
ModeView与ViewMode
与ViewModel的相同点是,都负责处理对Mode层数据向显示层数据的转换逻辑。
不同点:
ViewModel:隶属于Controller层(由Controller对象创建),属于Presentation Object。
ModeView:隶属于View层,实现为View类(如UIView)的子类,可直接用于显示。ModeView可以调用Model中数据获取的相关方法,获取用于显示的数据。
结论
MMVVC方法是在Smalltalk-80 MVC设计模式的基础上,通过增加ModelView层,对UI层中业务逻辑的编写进行了更为明确的定义:
- Controller:用户输入事件处理相关业务逻辑
- ModeView:UI显示相关业务逻辑
- View:业务逻辑无关的显示组件
通过将UI层业务逻辑的分离,为实现View、Controller、Model三层的开发解耦提供了一种可能性。