MVC\MVP\MVVM设计模式笔记

  • Who supposed to own networking request: a Model or a Controller?
    ( On apps requiring usage of an API, you have network access code, too. Sometimes that code is separate from the model and sometimes it’s included within the model. It’s never included in the view and should not be included in the controller if avoidable (it’s always avoidable).)
  • How do I pass a Model into a View Model of a new View?
  • MVC与MVVM的异同。
  • RAC在MVVM中扮演的角色。
  • 如果转变MVC为MVVM。

关于设计模式演化(其实是不断增多的需求来推动的)

  • 机器指令,简单的命令行,最早的程序是汇编
    script 像脚本一样开发。功能单一、管理单一、入口单一
  • 计算机被用于科学计算,产生了高级语言,当时的码农大多数是数学家
    程序只是输入输出的过程
  • 随着计算机的发展,开始做的事情多了,出现软件的概念
    软件的入口从指代Main函数编程了指代起始UI
  • 更高的UI需求,代码被分为描述什么(业务逻辑)和有什么(UI)两部分。
    code block 很好处理逻辑与UI在一起的关系,在同一个地方可以同时维护UI合逻辑
    code behind 能很好地处理UI和逻辑各自分开的关系,你可以让UI和逻辑各自做好各自的事情

当需求变得庞大,解决方案也会变得庞大;当解决方案变得庞大,就会出现细分,出现细分,就会出现按哪种方式管理的问题。软件从处理一件事务发展到了要处理许多事物。各事务间有包含、顺序、主次等关系。变得越来越复杂。因为数据与逻辑庞大了,所以数据与逻辑就分离了。然后事件和业务分离了。
大致分为:界面、数据、事件、业务

针对iOS来说(why care about choosing the architecture)

如果不使用合适的软件架构,那么终会有一天,你需要调试一个非常庞大,包含了众多业务逻辑的类,那时你就会发现自己根本无从下手。通常来说,开发者无法记住一个庞大的类的所有业务逻辑,因此在分析过程中,往往会因为类的内容过多而忽略掉很多重要的细节。

  • This class is the UIViewController subclass.
  • Your data stored directly in the UIViewController
  • Your UIViews do almost nothing
  • The Model is a dumb data structure
  • Your Unit Tests cover nothing
    (即使我们是听从了苹果官方开发的MVC模式,也会出现以上的问题。但是官方的MVC是有问题的。我们稍后会讲到。)

怎样才是好的设计模式呢(what's a good architecture)

  • Balanced distribution of responsibilities among entities with strict roles.(单一职责、平衡职责、分工明确)
    良好的分工可以让我们更好的理解代码。大脑对复杂问题的处理总是有极限的,所以处理复杂问题最好的办 法是遵循单一的职责的原则。
  • Testability usually comes from the first feature (and don’t worry: it is easy with appropriate architecture).(良好的可测试性)
    单元测试其实我不太熟悉,但是,对于深知单元测试好处的开发者来说。这并不是一个问题。单元测试可以大大的减少程序运行时才能发现的问题。这样可以节省 用户反馈->bug修复->新版本发布=>用户安装新版本这个耗时长达一周乃止以上的过程。This means the tests saved those developers from finding issues in runtime, which might happen when an app is on a user’s device and the fix takes a week to reach the user.
  • Ease of use and a low maintenance cost.(易用性,低维护成本)
    The best code is the code that has never been written.therefore the less code you have ,the less bugs you have.

MV(X)

View: 界面
Model:数据
Controller/Presenter/Viewmodel:the glue or mediator between M and V.

  • 有利于我们更好的理解各个部分的功能和指责,并且降低了各模块间的耦合度。
  • 使得View和Model更好复用。
  • 也有利于各个部分的独立测试。
    注意:这三种模式主要看的是mediator同View之间的关系。mediator和Model的关系都是相同的。

MVC

M-V-C.png

MVC的流程大概如下:

  1. A View triggers an action to happen on the Controller
  2. The Controller executes said action and updates the Model
  3. The controller may receive a notification from the model that it was updated.
  4. The Controller updates the View

代码:View通知Controller所有的用户操作,之后Controller更新Model,Model一般会通过kvo来通知别的Controller来更新View上的展示。

对于Controller来说:

  1. managing the view hierarchy of the view they own.
  2. respond to the view loading, appearing, disappearing, and so on .
  3. model logic(Models)
  4. business logic(Views)
    其中V和VC之间的界限模糊,是紧紧绑在一起的,Controller要维护View的生命周期,要接受View的事件,要对View进行定制。另一方面,Controller会更新Model也要响应Model的通知。似乎看起来Controller会变的很重。但这不是最严重的问题,最严重的问题是这种情况下View和Controller都会变的很重,变的难以复用。

iOS architecture, where MVC stands for Massive View Controller.

  • 厚重的View Controller
    由于大量的代码被放进view controller,导致他们变的相当臃肿。在iOS中有的view controller里绵延成千上万行代码的事并不是前所未见的。这些超重app的突出情况包括:厚重的View Controller很难维护(由于其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),导致协议的响应代码和controller的逻辑代码混淆在一起。
    厚重的view controller很难测试,不管是手动测试或是使用单元测试,因为有太多可能的状态。将代码分解成更小的多个模块通常是件好事。(Massive view controllers are difficult to test, either manually or with unit tests, because they have so many possible states. )
  • 遗失的网络逻辑
    苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个model,一个view,或是一个controller。就这些。那么把网络代码放哪里?和一个API通信的代码应该放在哪儿?
    你可能试着把它放在model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model生命周期更长,事情将变的复杂。显然也不应该把网络代码放在view里,因此只剩下controller了。这同样是个坏主意,因为这加剧了厚重View Controller的问题。
    那么应该放在那里呢?显然MVC的3大组件根本没有适合放这些代码的地方。
  • 较差的可测试性
    MVC的另一个大问题是,它不鼓励开发人员编写单元测试。由于view controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不做任何测试。
  • 定义模糊的“Manage”
    之前我提到了view controller可以管理试图的层次结构;view controller有一个“view”属性,并且可以通过IBOutlet访问视图的任何子视图。当有很多outlet时这样做不易于扩展,在某种意义上,最好不要使用子视图控制器(child view controller)来帮助管理子视图(subview)。
    要点在哪?验证用户输入的业务逻辑应归入controller还是model呢?
    在这里有多个模糊的标准,似乎没有人能完全达成一致。貌似无论如何,view和对应的controller都紧紧的耦合在一起,总之,还是会把它们当成一个组件来对待。

在MVC中,VC更像是一个整体。也叫做M-VC。

  • 耦合度 —— View 和 Model 是互相独立的,但是 View 和 View Controller 耦合度很高。
  • 可测试性 —— 只有 Model 可以脱离实际运行环境进行单元测试。(View 和 Controller 之间的通讯基本上是不能进行单元测试的。) Breaking your code up into smaller, more bite-sized pieces is typically a very good thing. A recent storycomes to mind. So that’s it: Unit Test models and networking code. Use UAT to test the rest, if it’s worth your time. Maybe manual testing the views and controllers would fit better into your work flow, especially for smaller apps. Finally, document whatever Unit Testing and UAT does not test, so you can be sure to manually test it before you ship.
    上述例子是否不大容易测试?虽然我们可以通过新建 GreetingModel 类并将 greeting 字符串的生成代码放到该类中来实现该部分代码的独立测试,但如果不调用 viewDidLoad didTapButton 方法,我们很多对 GreetingViewController 中 view 的显示逻辑(虽然上述例子没多少显示逻辑)进行测试。这也意味着在项目单元测试中,我们需要加载所有的 view,这对于单元测试来说是很糟糕的。mix view manipulation logic with business logic, separating out those components for the sake of unit testing becomes a herculean task.
  • 易用性 —— 相对于其他架构,代码量最少,而且大部分开发者对此架构都很熟悉,易用性良好。

Cocoa MVC is the pattern of your choice if you are not ready to invest more time in your architecture, and you feel that something with higher maintenance cost is an overkill for your tiny pet project.
Cocoa MVC is the best architectural pattern in terms of the speed of the development.(快速开发会用)

对于MVC来说,有一部分逻辑确实是属于 controller 的,但是也有一部分逻辑是不应该被放置在 controller 中的。比如,将 model 中的 NSDate 转换成 view 可以展示的 NSString 等。在 MVVM 中,我们将这些逻辑统称为展示逻辑

MVP

在 Apple 的 MVC 中,View 和 Controller 是紧密耦合的,但在 MVP 中,Presenter 与 View/View Controller 完全解耦,Presenter中没有任何与 View 布局相关的代码,View 可以很方便地进行移植。即便这样,Presenter 依旧肩负着对 View 的数据更新和动作捕捉。
耦合度 —— 在此架构中, Presenter 和 Model 模块的职责功能最分明,同时具备一个被尽量简化的
可测试性 —— 具备非常好的可测试性,我们可以通过 View 来测试大部分业务逻辑。
易用性 —— 对于我们这个没有实用性的简单例子来说,MVP 架构的代码量几乎是 MVC 的两倍,但同时,MVP 在思路上更加清晰。

MVVM

从图上看是比MVP简单了,更不用说MVC了。MVVM是一种软件架构模式,是MVC\MVP的演进,促进了UI代码和业务逻辑的分离(个人不认为MVVM是从MVP进化而来),我只觉得这是在MVP之后出现的一种“更好的”UI模式解决方案,但是用MVP来与之对比比较容易说明问题。
MVVM将表示逻辑从Controller里移除放到一个新对象里,即viewmodel。这样可以减少ViewController的复杂性,并使得表示逻辑更易于测试。ViewModel大致上就是MVP的Presenter和MVC的Controller了,而View和ViewModel间没有了MVP的界面接口,而是直接交互,用数据“绑定”的形式让数据更新的事件不需要开发人员手动去编写特殊用例,而是自动地双向同步。数据绑定你可以认为是Observer模式或者是Publish/Subscribe模式,原理都是为了用一种统一的集中的方式实现频繁需要被实现的数据更新问题。
比起MVP,MVVM不仅简化了业务与界面的依赖关系,还优化了数据频繁更新的解决方案,甚至可以说提供了一种有效的解决模式。

M-V-VM.png
  • model :与 MVC 中的 model 一致,包括数据模型、访问数据库的操作和网络请求等;
  • view :由 MVC 中的 view 和 controller 组成,负责 UI 的展示,绑定 viewModel 中的属性,触发 viewModel 中的命令
  • viewModel :从 MVC 的 controller 中抽取出来的展示逻辑,负责从 model 中获取 view 所需的数据,转换成 view 可以展示的数据,并暴露公开的属性和命令供 view 进行绑定;(只处理自己内部的state,state的更改会自动同步到view中,也叫数据绑定)
  • binder:在 MVVM 中,声明式的数据和命令绑定是一个隐含的约定,它可以让开发者非常方便地实现 view 和 viewModel的同步,避免编写大量繁杂的样板化代码。
    (MVVM 还使用了与 Supervising version MVP 架构类似的「绑定」机制;但是,这个「绑定」并非应用于 View 和 Model 之间,而是应用于 View 和 View Model 之间。在运行过程中,View Model 监听着 Model 的变化,并根据 Model 的变化来更新自身对应的变量,同时,由于在 View 和 View Model 间设置了「绑定」,View Model 的变化也会「触发」 View 的更新。view model和model的关系,和上面图中controller和model的关系是完全一样的。也就是说view model通过持有&更新model,通过响应model的通知来更新逻辑。区别在于和view的关系,是绑定的关系(提供一种松耦合的关系,可以帮我们复用view和view model))

优缺点:

  • 耦合度 —— 虽然在我们的例子中并不明显,但实际上 MVVM 中的 View 比 MVP 中的 View 具备更多的职责。在 MVVM 中 View 通过与 View Model 间的「绑定」来更新自身,而在 MVP 中,View 只是传递事件给 Presenter ,View 并不更新自身。
  • 可测试性 —— 鉴于View Model 对 View 一无所知,我们可以很容易地对 View Model 进行单独测试。虽然 View 同样可以做单元测试,但需要遍历所有页面。
  • 易用性 —— 与 MVP 架构相比,MVVM 具备几乎一样的代码量,但在实际项目中,若使用 MVP 架构,你需要传递所有 View 上的事件到 Presenter,同时在 Presenter 中手动更新 View ,而 MVVM 则不需要这样的操作,所以实际使用中 MVVM 会比 MVP 轻巧很多。
  • 可以兼容当下的MVC结构
分层模式

该图是推荐的viewmodel的实现方式,首先这是标准的mvvm的实现模式。
这里view model分为两部分,一部分是最基础的view model部分,只包含和的view的相关的一些属性,作为父类。所有的业务逻辑写在子类中,显然不同子类就可以实现不同业务线的业务逻辑。


优势

Under MVVM, the view and view controller become formally connected; we treat them as one. Views still don’t have references to the model, but neither do controllers. Instead, they reference the view model.
The one thing that does not belong in the view model is any reference to the view itself. The logic in the view model should be just as applicable on iOS as it is on OS X. (In other words, don’t #import UIKit.h in your view models and you’ll be fine.)

总结

在一个 app 中使用多种软件架构其实是很正常的。例如你的项目开始时使用的是 MVC ,后面你可能发现个别复杂的页面使用 MVC 架构实现时会变得难以维护,此时你可能会使用 MVVM 架构对该界面代码进行重构。但并不需要修改其他使用 MVC 架构的运行良好的页面代码。
对于MV(X)家族,都是在经典MVC基础上随着时代的发展、应用环境的变化衍变出来的。实现MV(X)模式的这些框架到底归属于哪种模式,也不必泥古。MV(X)是一个很有争议性的话题,能够构建一个健壮、具有良好设计、遵从关注点分离的项目比花时间去争论到底是MV(X)更有意义。

如何绑定?

举个例子:
Person:NSString *salutation
PersonViewController:一些判断以及格式化的逻辑 self.namelabel.text = self.model.salutation
加入Viewmodel后 (同样的代码只是移动了位置,便简化了VC,也方便了单元测试)
PersonViewModel: 一些判断以及格式化的逻辑
PersonViewController:只需要 self.namelabel.text = self.viewmodel.salutation

这个例子中的model是不可变的,所以只在初始化的时候指定我们的viewmodel的属性。对于可变model,我门还需要一些绑定机制,一旦viewmodel上的model发生改变,那么view的属性也需要更新,model的改变应该级联向下通过viewmodel进入view。

OSX : Cocoa
iOS :KVO (需要很多样板代码),ReactiveCocoa。
( • 基于 KVO 库设计的 RZDataBinding 和 SwiftBond

  • 基于函数式编程设计的库,如ReactiveCocoa, RxSwift和PromiseKit

    但是:MVVM并未强制我们使用ReactiveCocoa,MVVM是一个伟大的典范,它自身独立,只是再有一个良好绑定框架时做的更好。
    In practice, using ReactiveCocoa is a great way to glue all the moving pieces together.

ReactiveCocoa(函数式响应式编程框架)

reactive programming

在 iOS 开发中,系统并没有提供类似的框架可以让我们方便地实现 binder 功能。而GitHub开源的RAC为我们提供了一个不错的选择。
在 iOS 的 MVVM 实现中,我们可以使用 RAC 来在 view 和 viewModel 之间充当 binder 的角色,优雅地实现两者之间的同步。此外,我们还可以把 RAC 用在 model 层,使用 Signal 来代表异步的数据获取操作,比如读取文件、访问数据库和网络请求等。说明,RAC 的后一个应用场景是与 MVVM 无关的,也就是说,我们同样可以在 MVC 的 model 层这么用。

架构相关:MVVM With ReactiveCocoa
Basic MVVM with ReactiveCocoa(重要)
iOS中MVC已死MVVM当立?
Model-View-ViewModel for iOS(en)
MVVM 介绍
KVO实现MVVM
iOS 软件架构 - MVC, MVP, MVVM 和 VIPER 「译」 原文(en)
被误解的MVC和被神化的MVVM
https://objccn.io/issue-15-5/
MVVM 与 FRP 编程实战(有视频)
书籍:《FunctionalReactiveProgrammingOniOS》
测试相关:
如何对ViewModel进行单元测试
XCTest Assertions 及其种类
iOS 单元测试
What's Worth Unit Testing in Objective-C?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容