架构模式
关于架构模式
什么是架构模式
标准的定义:软件架构模式(Architectural Pattern) 是一种在软件系统中对模块进行职责划分、依赖管理与协作约束的设计思想,用来帮助开发者在复杂系统中保持结构清晰、逻辑解耦和可维护性。
简单通俗来说,架构模式就是在面向对象软件编程中给对象分门别类并建立或者约定它们之间关系的一种思想。这是我个人总结的通俗的说法并不一定完全准确,但我相信大体上没有错误并且能让大部分不熟悉,没接触过架构模式的人更加容易理解。而不是用一堆抽象的,专业的,让人摸不着头脑的术语来介绍什么是架构模式。不同的架构模式就表示着给对象分类的不同的指导思想,和对象之间如何协作的方式。
经过 AI 润色之后的通俗说法:架构模式是在面向对象程序设计中,用于划分类的职责并建立模块间协作关系的一种系统化思想,目的是提高代码的可维护性、可扩展性与可测试性。
架构模式其实是 组织代码的哲学和思想,而不是代码模板
- 它指导你怎么划分责任
- 对象间怎么通信
- 依赖关系应该朝哪个方向流动
很多新人刚学习写代码的时候,只知道在哪个地方写什么代码,后来稍微懂了一点之后发现代码好像写在哪里都可以成功运行,不影响最终结果。只有在接触了架构模式之后才真正能理解代码应该写在哪里,为什么要写在那里,某个类应该做什么,不应该做什么。
为什么要给对象分门别类呢?
不分类不行吗?严格来说不分类也没有任何问题。但在实际的项目开发过程中,你的公司会给项目不停的新增需求,如果不使用架构模式进行管理,或者架构模式选择的不合适,使用的不正确,就会自然而然的导致
- 代码维护困难,
- 新增功能麻烦,
- 无法对新增功能进行单元测试,
- 修改 bug 时,按下葫芦起了瓢,完全搞不明白 bug 产生的原因
架构模式解决了什么问题
解决了软件开发过程中,不断新增的需求导致项目越来越难以开发新功能,新需求,测试,维护的问题。
更简单点来说解决了复杂软件项目的管理问题?
都有什么架构模式
除了移动端有架构模式,后端,前端也有它自己的架构模式。移动端比较知名的架构模式有 MVC、MVP、MVVM、VIPER。这些具体的架构模式都会在后文中详细介绍。
- 移动端知名架构模式:
- MVC
- MVP
- MVVM
- VIPER
- MVI
- 前端知名的架构模式:
- MVC/MVVM
- Flux/Redux
- Component-Base Architecture
- Clean Architecture / Atomic Design
- Micro Frontends
- 后端知名的架构模式
- 分层架构(Layered Architecture)
- Clean Architecture / Hexagonal Architecture / Onion Architecture
- 微服务架构(Microservices Architecture)
- 事件驱动架构(Event-Driven Architecture)
- Serverless / Function as a Service 架构
应该说架构模式是一种思想,它并不局限于在什么端,不论是前端后端还是移动端都存在 MVC 架构模式,以及基于或者不基于 MVC 思想的其他架构模式。而移动端和前端最核心的最需要解决的问题是用户输入和视图展示之间的复杂映射。可以将带用户界面的架构模式归为一类如移动端和前端中常见的架构模式,而不带用户界面的架构模式如后端服务、命令行工具归为一类。
架构模式会不会带来新的问题
会的,架构演进过程中的核心矛盾就是:
方便性(短期开发效率) vs 可维护性(长期扩展能力)
以移动端常见的架构模式为例:
MVC 阶段
- 优点:ViewController 可以直接访问 Model、直接操作 View、写起来直观、快速、好理解。
- 缺点:随着需求的增加、ViewController 膨胀成巨无霸,几千行代码难以维护。
MVP/MVVM/VIPER 等阶段
- 优点:
- 通过引入 Presenter、ViewModel、Interactor 等角色,让 ViewController 只专注于界面层
- 业务逻辑被拆分到不同模块、职责单一、易于测性、易于扩展
- 缺点:
- 复杂度增加,数据和视图之间需要额外的协议、绑定、通知
- 有些数据、视图,无法直接在 ViewController 中拿到,需要走一层甚至多层转发。
架构模式每设计的多一层,会换来结构清晰和易维护易扩展,但是却牺牲了方便性,可读性。所以架构模式应该根据项目来决定:
- 小项目/Demo/原型:MVC 就足够了,都写在 Controller 里快准狠。
- 中等规模项目:MVP 或轻量 MVVM,适度拆分逻辑。
- 大型复杂项目(多团队协作、生命周期长):MVVM + 数据绑定框架,甚至 VIPER,把职责拆的非常细。
架构就是在短期便利性和长期维护性之间找平衡。没有一个万能架构适合所有规模项目,只有适合当前团队和当前规模项目的架构。而且架构也是可以随着团队发展和项目规模发展动态改变的。
架构模式很抽象,很难学习理解吗
客观上来讲,是的,它很抽象因此有一定的难度,它关乎“宏观布局”而非“微观代码”。但它并不难理解,但前提是你要有足够的知识储备和编码经验,对于没什么实际编程经验的编程新人来说肯定是抽象和难以理解的。完全可以通过实际的项目来理解各种抽象的架构模式。
大多数程序员是从写代码(函数、变量、循环)开始的。这种视角是微观的。而架构模式要求你切换到宏观视角。
- 视角转换的阵痛: 就像一个习惯了“砌砖”的工人,突然让他去画整栋大楼的蓝图,他会觉得无从下手。架构模式不关心 if-else 怎么写,它关心的是“厨房应该放在客厅旁边”还是“放在二楼”。
- 没有银弹(No Silver Bullet): 架构模式没有绝对的对错,只有权衡(Trade-offs)。初学者往往在这个“既可以这样,又可以那样”的选择中感到迷茫。
- 缺乏“痛点”体验: 很多架构模式是为了解决大规模、高并发或复杂业务带来的混乱。如果你只写过简单的 Demo,你可能根本感受不到这些模式存在的必要性,因此觉得它们是“为了复杂而复杂”。
好的架构模式代码就一定很少吗
首先,架构模式并没有好坏之分,只有适合与不适合当前项目规模之分。其次,我把好的架构模式理解成复杂的架构模式如 VIPER 和简单的架构模式如 MVC,这么来说的话。复杂的架构模式并不能保证代码量就一定少,甚至在大部分情况下可能代码量要更多。架构模式和代码量之间并没有必然的关联性。
移动端常见软件架构模式深度对比:MVC, MVP, MVVM, MVI, VIPER
概述
MVC, MVP, MVVM, MVI, VIPER 这些架构模式是专门为 UI 驱动(Event-Driven)的应用设计的。 它们存在的核心价值是处理“用户输入(Input)”与“视图展示(Output)”之间的复杂映射。
对于没有 UI 界面的程序(如后端服务、命令行工具、嵌入式逻辑库、数据处理引擎),通常不会使用这些模式,而是遵循另一套架构准则。
为什么无 UI 程序不用这些架构?
- 没有 View:架构缩写里的 V 根本就不存在。
- 没有 Data Binding 需求:不需要实时同步内存中模型的状态到屏幕,因此 MVVM 的 VM 失去了意义。
- 触发机制不同:无 UI 程序通常是“请求-响应”模式(RPC/API)或“流处理”模式,而不是基于用户点击事件的。
一、模型-视图-控制器 (Model-View-Controller, MVC)
诞生历史与发展
| 组件 | 角色 |
|---|---|
| Model (模型) | 负责管理数据和业务逻辑。与持久化存储交互,并通知 View 或 Controller 数据变化。 |
| View (视图) | 负责显示用户界面 (UI)。通常是“笨”组件,不包含任何业务逻辑。 |
| Controller (控制器) | 接收用户输入,选择 Model 来处理请求,并决定哪个 View 来显示结果。是 View 和 Model 之间的协调者。 |
- 诞生: MVC 是最早的架构模式之一,由 Trygve Reenskaug 在 20 世纪 70 年代末首次提出,并于 1988 年在 Smalltalk-80 中首次被正式描述和实现。
- 发展: 它迅速成为桌面 GUI 应用的主流模式。在 Web 时代,它被 Ruby on Rails、Django 等框架广泛采纳。在早期 iOS (Cocoa Touch) 中,MVC 是主要的范式,但其在移动端的使用带来了 “Massive View Controller”(庞大的视图控制器) 的问题。
现状与挑战
- 现状: 依然是 Web 后端框架的主流。在移动端(尤其是 iOS),由于 View Controller 承担了太多责任(生命周期、网络请求、数据处理等),它更多被视为一种基础思想,并被更现代的模式所取代。
- 挑战: 紧密耦合是最大的问题。View 和 Controller 经常直接互相引用,导致 View Controller 变得臃肿,难以单元测试。
二、模型-视图-展示器 (Model-View-Presenter, MVP)
诞生历史与发展
| 组件 | 角色 |
|---|---|
| Model (模型) | 同 MVC,负责数据和业务逻辑。 |
| View (视图) | 负责显示 UI 并将用户操作转发给 Presenter。View 包含一个 Presenter 的引用。 |
| Presenter (展示器) | 接收 View 的事件,处理逻辑,从 Model 获取数据,然后通过 View 接口直接命令 View 更新 UI。Presenter 包含一个 View 接口的引用。 |
- 诞生: MVP 诞生于 20 世纪 90 年代初的 IBM,作为 MVC 的变体,旨在解决在某些框架(如 Smalltalk/VisualWorks)中 Controller 过于依赖 View 的问题。它在 2000 年代初期随着微软的 .NET WinForms 社区再次流行。
- 发展: 在 Android 早期开发中广受欢迎,因为它通过引入 Presenter 隔断了 View 和 Model 的直接联系,有效解决了 Android 中 View(Activity/Fragment)难以测试的问题。
现状与挑战
- 现状: 在 Android 开发中,仍然有许多遗留项目或特定团队使用。但随着 MVVM 和 Jetpack 的兴起,其使用量有所下降。
- 挑战: View 和 Presenter 之间的双向接口引用是其主要缺点(尽管这保证了高测试性)。需要编写大量的接口代码(Protocol),代码量增加,且 Presenter 仍然通过命令式地调用 View 的方法(如 view.showData(data))来更新 UI,属于命令式编程。
三、模型-视图-视图模型 (Model-View-ViewModel, MVVM)
诞生历史与发展
| 组件 | 角色 |
|---|---|
| Model (模型) | 同上,负责数据和业务逻辑。 |
| View (视图) | 负责显示 UI。它不再直接持有 ViewModel 的引用,而是通过数据绑定 (Data Binding) 来观察 ViewModel 的变化。 |
| ViewModel (视图模型) | 封装了 View 所需的所有状态和操作。它从 Model 获取数据,并以 View 容易消费的方式暴露出去。它不持有 View 的引用。 |
- 诞生: MVVM 由微软架构师 John Gossman 于 2005 年为 WPF (Windows Presentation Foundation) 和 Silverlight 平台设计,是 MVP 的进一步优化。
- 发展: 它的核心思想是数据绑定。View 和 ViewModel 之间的通信是通过响应式/声明式的数据流(如 LiveData, StateFlow, Combine, RxJS)实现的。它彻底改变了 View 的更新方式。在 Android (Google 官方推荐)、iOS (SwiftUI/Combine) 和前端 (React/Vue/Angular) 中成为最流行的模式。
现状与优势
- 现状: 移动端和前端应用的主流架构。 Android 的 Jetpack/LiveData/ViewModel 体系是其完美实现;iOS 的 SwiftUI 和 Combine 也是基于 MVVM/Combine 的响应式范式。
- 优势: 低耦合,因为 ViewModel 对 View 一无所知(没有 View 的引用)。高测试性,只需测试 ViewModel 中的业务逻辑。View 的更新是声明式的,减少了命令式代码。
四、模型-视图-意图 (Model-View-Intent, MVI)
诞生历史与发展
| 组件 | 角色 |
|---|---|
| Intent (意图) | 用户操作的抽象(如:LoadDataIntent, ClickButtonIntent)。是单向数据流的起点。 |
| Model (模型) | 实际上指的是**状态 (State)**,是应用在某一时刻的单一、不可变的状态对象。 |
| View (视图) | 负责渲染当前的状态,并将用户输入转化为 Intent 传递给处理层。 |
- 诞生: MVI 模式从 Web 端的Flux/Redux和函数式响应式编程 (FRP) 中获得灵感,由 Cycle.js 推广,并在 2010 年代中后期被 Android 社区(如 Mosby 框架的作者 Hannes Dorfmann)引入。
- 发展: 它强制使用单向数据流 (Unidirectional Data Flow, UDF) 和 不可变状态 (Immutable State) 。所有用户操作都封装为 Intent,经过处理后产生一个新的状态,View 仅负责渲染这个新状态。
现状与特点
- 现状: 在强调状态管理的现代框架中越来越受欢迎,尤其是在 Kotlin/Compose 和 SwiftUI 社区。它提供极高的一致性和可预测性。
- 特点:
- 单一数据源 (Single Source of Truth): 只有一个不可变的状态对象。
- 可预测性高: 因为数据流是严格单向的,调试和重现 Bug 变得非常容易。
- 代码量高: 由于 Intent 和 State 的封装,通常会比 MVVM 增加更多的样板代码(Boilerplate)。
五、视图-交互器-展示器-实体-路由 (View-Interactor-Presenter-Entity-Router, VIPER)
诞生历史与发展
| 组件 | 角色 |
|---|---|
| View (视图) | 接收用户输入,并将其传递给 Presenter。由 Presenter 负责更新。 |
| Interactor (交互器) | 包含所有业务逻辑。 与 Entity 和网络/数据库服务交互。 |
| Presenter (展示器) | 介于 View 和 Interactor 之间,处理 View 的事件,调用 Interactor,并将 Interactor 返回的数据格式化后传递给 View。 |
| Entity (实体) | 纯粹的数据对象/模型。 |
| Router (路由) | 负责处理导航逻辑(从一个屏幕跳转到另一个屏幕)。 |
- 诞生: VIPER 诞生于 2014 年左右,由 Clean Architecture(“清洁架构”)思想驱动,并被 Mutual Mobile 推广,旨在解决大型 iOS 应用中 MVC 导致的庞大 View Controller 问题。
- 发展: 它将 MVP 的 Presenter 进一步拆分,把业务逻辑移入 Interactor,把导航逻辑移入 Router。它追求极致的解耦。
现状与特点
- 现状: 主要在大型、复杂的 iOS 应用程序中流行,这些应用需要严格控制模块间的依赖。由于其高复杂度和大量的接口,不适合小型项目。
- 特点: 极高解耦,每个组件都只与相邻的组件通信,且通信通常通过接口完成。这带来了极高的可测试性,但代价是极高的复杂性和大量的样板代码。
六、五大架构模式对比总结
下图清晰地展示了这些模式的核心差异和数据流向。
| 特性 / 模式 | MVC | MVP (Passive View) | MVVM (Data Binding) | MVI (State/Intent) | VIPER (Clean Arch) |
|---|---|---|---|---|---|
| 耦合度 | 高 (View <-> Controller) | 中 (View <-> Presenter 接口) | 低 (View <-> ViewModel 绑定) | 低 (单向数据流) | 极低 (职责划分细致) |
| 可测试性 | 低 (Controller 包含 UI 依赖) | 高 (Presenter 独立于 UI) | 高 (ViewModel 独立于 UI) | 极高 (状态机可预测) | 极高 (所有组件可独立测试) |
| 复杂性 | 低 (结构简单) | 中 (需要编写接口) | 中 (需要响应式框架) | 中高 (需要处理 Intent/State) | 极高 (组件数量多) |
| 通信方式 | View/Controller 直接调用 | Presenter 命令 View | 声明式数据绑定/响应式 | 单向数据流 (UDF) | 接口/协议调用 |
| 典型平台 | Web 框架 (Rails, Django), 早期 iOS | Android (早期/特定项目) | Android, iOS, Vue, React, Angular | Android Compose, SwiftUI, Redux/Flux | 大型 iOS 应用 |
总结:
- 如果你正在开发一个大型、复杂的、需要长期维护和严格测试的 iOS 项目,VIPER 值得考虑。
- 如果你正在开发一个现代的、状态驱动的、需要高可预测性的应用,MVVM 或 MVI 是最佳选择。MVVM 在 Android 和现代 Web 中是最流行的。
- MVC 和 MVP 仍是理解其他模式的基础,但对于现代移动应用开发而言,它们通常被视为不够理想的实践。
MVC
Apple 官方的 MVC 指的是 Model、View、Controller。ViewController 持有 View 和 Model,内部处理协调 View 和 Model 的逻辑。Apple 的 UIKit 框架就是典型的基于 MVC 架构思想开发出来的,UIKit 框架中的视图框架如 UITableView,UICollectionView 等视图都是典型的 MVC 架构中的 View 层,这些视图不持有任何模型数据。而是在视图控制器 UIViewController 中给视图提供数据。这样的做法让 UIKit 的视图得到了充分的复用。但是这种情况下,ViewController 里面的代码会随着业务的增加导致越来越臃肿。因为所有的代码都写在 ViewController 里面了。
MVC 中的 Controller 不是只有 ViewController,也可以有 ModelController。Controller 可以有通用的,也可以有专用的。总之 MVC 并不是一定会导致 ViewController 的臃肿,ViewController 臃肿的根本原因不是 MVC 架构,而是初学者根本不知道如何避免 ViewController 的臃肿。MVC 最大的问题在于,它仅把对象分为三类,实际项目开发中的对象所属的类应该说远不止这三种,这就导致了职责不清晰。然后很多人把只要不是 Model,View 的代码都写在了 ViewController 中才导致臃肿的 ViewController。
比如说,业务逻辑写在哪里?网络请求写在哪里?数据存储写在哪里?页面交互写在哪里?MVC 并没有明确规定,那么业务逻辑,网络请求,数据存储,页面交互这些看起来很明显即不是视图,也不算模型,那么自然而然的就都写到了视图控制器中去了。
优点
- View 和 Model 的可重用性高。
- 适合个人或者小团队的小项目,或者项目发展初期追求速度快速开发出第一个可以运行的项目,暂时不考虑,可扩展,可维护性的时候使用。
缺点
- 如果没有刻意的维护,Controller 内的代码会不知不觉的越来越多。
- 不适合大型的,多人共同开发的项目
案例
作者这里将 VIPER 架构中的 Demo 项目改造成了 MVC 版本的,或者说是大多数人在工作中经常遇到的胖 MVC 版本。
在 VIPER 架构的 Demo 中,文件组织的其实非常好,我们新的 MVC 架构项目可以沿用。MVC 版本的项目整体文件夹结构如下:
1 | ~/MVC-TODO/MVC-TODO/Classes/ [main+*] tree |
其中 Common 文件夹部分,移除掉了一些不是必要的的文件,最终只保留了必要的 Categories、Model、Store 下的文件。Modules 文件夹很清晰的分为两个部分,Add 添加待办事件模块和 List 待办事项列表模块。同样都只保留的必要的部分文件,从文件数量上来说明显比 VIPER 清爽不少,但也很难说这种清爽到底是好还是不好。
根控制器的显示
VIPER 架构中的 VTDAppDependencies 类是用来给 VIPER 架构中的各个类之间建立依赖关系的,在 MVC 中我也给去掉了,很难说这一步是不是必要的,但是我认为直接在 AppDelegate 或者 SceneDelegate 中设置应用的根控制器是绝大多数 iOS 开发者更加容易接受,更常见到的做法。我将这一部分写到了 SceneDelegate 中,代码如下:
1 |
|
VTDListViewController 的修改
接下来就到了由 VIPER 架构改为 MVC 架构过程中改动最大的部分:VTDListViewController 类。头文件部分还好,它持有的协议对象,和它需要实现的协议都被注释掉了,在我们的 MVC 中暂时还不需要这些协议。在 VIPER 架构中经常能看到这种用法,就是类不持有一个具体类的实例而是持有一个特定协议的实例,同时这个遵守协议的实例它也不持有具体类的实例,同样持有一个遵守协议的实例。
如:
- 类
VTDListViewController自己实现了VTDListViewInterface协议,它持有id<VTDListModuleInterface> eventHandler,这样任何一个类只要实现了VTDListModuleInterface协议就能成为VTDListViewController的eventHandler。 - 类
VTDListPresenter实现了VTDListModuleInterface协议,所以能成为VTDListViewController的eventHandler。它持有UIViewController<VTDListViewInterface> *userInterface,这样就不需要依赖任何一个具体的视图控制器,任何实现了VTDListViewInterface协议的视图控制器都能成为userInterface。呃VTDListViewController类显然已经实现了VTDListViewInterface协议。
这样的做法实现了两个类之间的解耦合,UI 相关的类 VTDListViewController 不依赖具体的事件处理者类 VTDListPresenter,事件处理者类也不依赖具体的 UI 类是什么。这种通过协议进行解耦合的方法才是架构模式的本质。可能很多读者不能理解什么叫不依赖,可以简单的理解为不需要导入头文件,比如 VTDListViewController 可以看到它根本不需要导入太多的头文件。VTDListViewController.h 只导入了两个协议,一个是它需要实现的协议,一个是它持有的对象需要的协议。VTDListViewController.m 它只需要导入必要的头文件,比如它用到的组模型,行模型,结合组模型和行模型的 DisplayData 模型就完全足够了,这个 VTDListViewController 就是低耦合的典范。
这种写法应该叫面向接口编程,就是类的功能实现不依赖具体的类,而是依赖接口。在 Objective-C 中实现面向接口编程的方式有蛮多种,但最推荐的就是协议。语言级接口,最自然的“面向接口”方式。可以声明必须/可选方法,容易 mock 和单元测试,配合 id
然后就是 VTDListViewController.m 文件的改动了。在 VIPER 架构中 VTDListViewController.m 看着确实非常清爽,整个 VTDListViewController.m 中只存在与 UI 相关的代码。这也正是视图控制器的职责所在,就是说只做自己应该做的事情,保持低耦合高内聚。一些 UI 相关的事件,如点击添加待办事项按钮,页面将要显示,的确会触发视图控制器的某些代码,如 viewWillAppear:、didTapAddButton: 但实际查看这些方法的实现就知道,视图控制器并没有真正处理这些事件,而是转交给 id<VTDListModuleInterface> eventHandler 去处理了。
回到我们的 MVC 架构版本中。会看到很多熟悉的方法回来了,比如 loadData,多了很多与 UI 无关的代码,如:
- 本地存储和数据处理:
loadData方法中直接使用使用 CoreData 查询待办事项数据,并对数据进行处理转成当前列表视图需要的数据。 - 页面跳转:如在
didTapAddButton:方法中直接创建VTDAddViewController并进行了跳转。
具体的代码如下:
1 |
|
这都是在现实的 iOS 项目中经常遇到的 MVC 写法,但是经常遇到这么做,大家都这么写并不能说明这就是正确的,这只能说明大部分程序员并没有架构思想。。。UIViewController 它是视图控制器,它应该只处理与视图相关的逻辑,而不应该将任何与视图无关的代码都写在视图控制器中。
很多人用 MVC 写成巨大怪物,是因为:
❌ 他们把所有业务塞进 ViewController 然后再怪 MVC 不好。再去尝试其它各种架构,但是使用或者领悟不到位,其他架构也越写越不像样。。。
当然也不是说,MVC 这么写就一定不行,其实对于目前这个 Demo 项目的规模来说就写成这样也没太大的毛病。一个人的小项目不必要追求什么可维护性,可扩展性,可重用性的。当然,如果你对架构设计有追求,想要让视图控制器里保持干净,那么可以先试试接下来的 MVP 架构。
为什么不直接使用 VIPER 架构呢?一步步来嘛,从架构的细分层度上来说,MVC 是分的最粗糙的只有 Model,View,Controller。VIPER 是分的最细的 View/ViewController,Interactor,Presenter,Entity,Router。而 MVP 在中间,Model,View/ViewController,Presenter。
对于我们这个待办事项 APP 来说,MVP 可能是最合适的架构,不至于像 VIPER 分的过于仔细,也不像 MVC 这样所有代码都写在 VIewController 内。
MVP
一句话总结:MVP 就是给胖 UIViewController 找个“助手(Presenter)”,把与 UI 无关的代码挪过去,UIViewController 专心管 UI。虽然说起来一句话很简单,看起来架构也很简单,但是如果你从未实现过一次这样的改动的话,第一次让你动手实现时会感觉无从下手,不知道从哪里开始。别担心,这是一个非常常见且正常的困惑!
我们继续使用前面的 MVC 架构的待办事项项目,并着手对其中最复杂的 VTDListViewController 进行改造,使其能像在 VIPER 架构中那样的清爽。
1.创建 Presenter 类并转移逻辑
这是第一步,先不考虑协议,把代码挪过去。
新建 Presenter 类。为你的 UIViewController 创建一个对应的 Presenter 类,在这里为
VTDListViewController创建一个VTDListPresenter类。转移
VTDListViewController中非 UI 逻辑:- 将所有的数据处理、业务逻辑(数据获取、计算、排序、格式化等)的方法和属性从视图控制器剪切然后粘贴到 Presenter 类中。
- 例如:网络请求方法,加载本地数据库方法
loadData,处理按钮点击后的数据更新逻辑、决定数据模型(Model)状态的属性等。
建立连接:
- 在视图控制器中添加一个属性来持有 Presenter 类的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@interface VTDListViewController () <UIViewControllerTransitioningDelegate>
// ...
@property (nonatomic, strong) VTDListPresenter *listPresenter;
// ...
@end
@implementation VTDListViewController
/ ...
- (VTDListPresenter *)listPresenter {
if (_listPresenter == nil) {
_listPresenter = [[VTDListPresenter alloc] init];
_listPresenter.userInterface = self;
}
return _listPresenter;
}
// ...
@end
2.设计 View 协议
现在开始解决如何在 Presenter 中通知 View/ViewController 更新 UI 的问题。
定义协议(Protocol):创建一个协议,通常命名为
XXXViewProtocol例如:VTDListViewProtocol或者VTDListViewInterface谁来实现?谁来持有?
- 实现者(Implementer):顾名思义既然是视图协议,当然是让 View/ViewController 来实现这个协议。因为 Presenter 需要通过协议对象来更新 UI。
- 持有者(Holder):Presenter 持有一个该协议类型的弱引用(Weak Reference)属性。
协议内容:协议中只包含纯粹的 UI 更新方法。这些方法应该是 Presenter 向 View/ViewController 发出的命令,告诉它你该现实什么,而不是你应该做什么。
- 例如:
1
2
3
4
5
6
7
8
9
10@class VTDUpcomingDisplayData;
@protocol VTDListViewInterface <NSObject>
- (void)showNoContentMessage;
- (void)showUpcomingDisplayData:(VTDUpcomingDisplayData *)data;
- (void)reloadEntries;
- (void)presentViewControllerWithMinDate:(NSDate *)minDate;
@end
3.实现 View 协议并建立连接
在 View/VIewController 中实现刚刚创建的协议:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)showNoContentMessage
{
self.view = self.noContentView;
}
- (void)showUpcomingDisplayData:(VTDUpcomingDisplayData *)data
{
self.view = self.strongTableView;
self.data = data;
[self reloadEntries];
}
- (void)reloadEntries
{
[self.tableView reloadData];
}让 Presenter 持有一个协议对象:
1
2
3
4
5
6
7@protocol VTDListViewInterface;
@interface VTDListPresenter : NSObject
@property (nonatomic, weak) id<VTDListViewInterface> userInterface;
@end现在,就可以在 Presenter 中在需要的时候让 View/ViewController 更新 UI 了。
1
2
3
4
5
6
7
8
9
10[VTDCoreDataStore.sharedStore fetchEntriesWithPredicate:predicate sortDescriptors:sortDescriptors completionBlock:^(NSArray<VTDManagedTodoItem *> *entries) {
// ...
if (entries.count == 0) {
[welf.userInterface showNoContentMessage];
return;
}
// 数据处理...
[welf.userInterface showUpcomingDisplayData:displayData];
// ...
}];
4.设计 Presenter 协议
这一步是可选的,因为此时在 View/ViewController 中对 Presenter 的持有还是显示依赖的,就是在 View/ViewController 中显示创建了 Presenter 实例。
为了进一步解耦 ViewController 对特定 Presenter 类的依赖,使单元测试(Unit Test)更容易。
- 定义 Presenter 协议:创建一个协议,通常可以命名为
XXXPresenrterProtocol或者,这个例子中的VTDListModuleInterface表示列表模块接口 - 谁来实现?谁来持有?
- 实现者:Presenter 类实现这个协议
- 持有者:View/ViewController 持有一个该协议类型的属性,通常命名为
presenter,或者eventHandler等。。。
- 协议内容:包含所有 View/ViewController 需要调用 Presenter 的方法。
例如:
1 |
|
这样 VTDListViewController 中最终呈现的效果就比 MVC 中干净简洁不少,但跟 VIPER 比起来还差一点点。主要是 视图控制器 的路由跳转部分并没有挪到专门的模块中去。在 MVP 中来说也并不是必须得。
VTDListViewController 最终代码如下:
1 |
|
MVP 小结
View 持有 Presenter 协议;Presenter 持有 View 协议。
VIPER
关于 VIPER 的部分,推荐直接阅读以下链接文章。
https://www.objc.io/issues/13-architecture/viper/ 这是对 VIPER 的介绍非常详细和全面的文章
是对 MVP 的进一步细化。
MVVM
关于 MVVM 的部分,推荐直接阅读以下链接文章。
https://www.objc.io/issues/13-architecture/mvvm/ 这个对 MVVM 介绍的文章
架构模式总结
架构名字是“虚名”,解耦能力才是本质。架构模式(MVC、MVVM、VIPER、MVP、Clean Architecture 等)都是组织代码的“模板”,
但真正决定你代码质量的是:
- 你对模块之间的边界如何划分?
- 你如何做到解耦?
- 你如何做到可替换、可测试、可维护?
架构名称只是“如何组织解耦的最佳实践示例”。
1. MVC 中也可以面向接口编程(protocol、抽象类、组合、DI 全都可以用)
很多人用 MVC 写成巨大怪物,是因为:
❌ 他们把所有业务塞进 ViewController,然后再怪 MVC 不好。
但:
完全可以写出高度解耦的 MVC
完全可以在 MVC 中使用 protocol + DI
完全可以把网络、业务逻辑、数据层抽离出去
甚至苹果自己的 UIKit 官方示例 MVC 都不是“胖 VC”,都大量使用 delegate、dataSource、protocol 等机制。
举例 MVC 也可以这样写:
1 | ViewController —-> (protocol) BusinessLogic |
这看起来像 VIPER,对吧?
但你依然可以叫它 MVC,因为命名并不重要。
2. 架构名称(MVC、MVP、VIPER)本质是“关注点分离指导方针”
本质其实只有两个字:
⭐ “分层”
⭐ “解耦”
架构模式做的事只有:
| 架构名 | 目的 |
|---|---|
| MVC | 把 UI 与 Model 分开 |
| MVP | 把 UI 与业务 Presenter 分开 |
| MVVM | 把 UI 与状态转换逻辑分开 |
| VIPER | 把 UI、业务、路由、存储强制分离 |
| Clean Architecture | 强调依赖规则(高层不依赖低层) |
都在解决一个问题:
如何降低耦合,让系统更易维护?
3. 真正核心技能:
⭐ 面向接口编程(Protocol-Oriented Programming + DI)
这才是:
- 可测试性
- 可替换性
- 可维护性
- 可扩展性
的根源
所有现代架构都是围绕它展开的。
4. 所以真正高级工程师不会执着于“我在用 VIPER/MVVM/MVP…”
高级工程师关心的是:
✔ 分层是否清晰?
✔ 依赖反转是否做到?
✔ 模块之间是否通过 protocol 通信?
✔ 代码是否可替换?可测试?
✔ 业务逻辑是否与 UI 隔离?
✔ 一处修改是否不会引发连锁 bug?
架构名称只是形式,架构思想才是核心。
你完全可以写出这样的 MVC:
1 | ViewController |
这是经典的可扩展架构,但你仍然可以说它是“MVC 风味”。
5. 最终结论
✔ 架构名称不重要
✔ 分层+解耦才是关键
✔ 协议(接口)才是 Objective-C 和 Swift 解耦的核心武器
✔ 任何架构模式都能写得很糟,也能写得很好
✔ 你可以用 MVC 写出可测试、可维护、可扩展的大型应用
✔ 真正的专业能力不是“会 VIPER”,而是“会解耦”
