软件开发活动是整个流程的核心环节:接收产品和视觉设计需求/变更作为输入,然后输出客户可用的终端产品。
而统一的软件开发架构模式,则是我们保障软件开发质量的基础。(这里就不具体展开WHY了)
由于讨论的是具体面向客户使用的业务场景,少不了客户操作交互的视图层(View),所以我从MVC开始谈起。
从表现层的MVC谈起
我之所以没有在上图中对M-V-C添加箭头线条,是因为在这一点上,不同程序员也有不同理解和实践。(2)
这是第一个需要明确的点:MVC架构模式在多层系统架构里的应用范围。
左侧 业务表现层-业务服务层-基础服务层 是移动端三层架构模式,未涉及到 C/S 交互;右侧是Web B/S场景的三层架构模式。
因为有些应用会比较简单,根本不需要业务服务或基础服务层,纯粹靠一个MVC(或者VC)就能交付出一个Mobile/Web App;而且在一些业务系统里,Web前端/桌面客户端/移动App 也可能会被简化为 大前端/大终端表现层;所以可能基于不同信息,不同程序员对此会有不同认知。
但随着用户终端应用的重要性和复杂度的提升,已经从简单应用发展到复杂多团队协同的平台型或航母级应用,仅靠一个MVC来完成交付是不合适的。
也可以反过来想,程序员会把以下代码放在客户端代码的哪一层:
-
对Web引擎的扩展逻辑。
-
通信协议的结构定义,以及相应的socket连接和通信代码。
-
一个业务相关且UI无关的平台开放能力。
-
Crash捕获、卡顿监控、日志埋点等功能实现,比如Android在做APM相关事情时会采用AOP方式,利用ASM、AspectJ等方案来做字节码插桩。
-
……
? 业界基于MVC模式的不同实践
前面提到不同程序员对MVC模式的理解和实践存在差异,业界大厂亦然,比如苹果、微软,在自身实践MVC模式或者向开发者提供相关文档时,也一样有不同的说法。
-
微软
可以看出微软在MVC的实践上:
-
Controller可以引用View和Model。
-
View可以引用Model。
-
这里的Model倾向于是Passive。
值得注意的是,微软在文档里写到:
-
强类型视图通常使用 ViewModel 类型,旨在包含要在该视图上显示的数据。 控制器从模型创建并填充 ViewModel 实例。
-
控制器不应由于责任过多而变得过于复杂。 要阻止控制器逻辑变得过于复杂,请将业务逻辑推出控制器并推入域模型。
-
苹果 & 斯坦福
和上面微软、苹果给的图,又不一样了 *_*
-
Google
谷歌相关的描述则是:
问题一:如何解决MVC中Controller的膨胀臃肿问题p>
要回答如何解决,需要先思考为什么膨胀。
问题二:View能否引用Modelp>
-
要回答能否引用,需要先定义引用关系是什么。
是持有对象,还是调用CURD接口操作对象。
又或者这两者没有必要区分,因为持有的对象本身就可能带CURD接口。
-
参考上面相关资料,目前(1)微软2021年文档支持可以引用;(2)苹果/斯坦福2011年文档反对。
-
在DinamicX研发模式中,模板View持有data数据对象在内部进行填充和渲染。
问题三:存在View -> Model,那么是否可以反过来存在 Model -> Viewp>
和问题二在描述上相反但又有关联,如果对问题再进一步提问的话:
-
使用 -> 引用关系,是为了解决什么问题p>
使用 -> 引用关系,会产生什么问题p>
如同文章开头所说,以上问题需要结合具体场景来展开(见 实际案例结合),尽量从务虚到务实。
? Redux-like Architecture and Framework
随着前后端分离得更彻底,终端设备性能和用户体验重要性的提升,前端领域也得到了蓬勃发展,开发方式也有了比较大的变化,MVC-like方式不再是主流:
-
UI开发方面从早期的命令式到现在的声明式。
-
整体应用和业务逻辑实现方面,从早期的OOP写法,转向基于FP的响应式编程。比如Redux的数据流、React的Hook特性等。
-
各种框架蓬勃发展,一些概念和模式的提出、实践应用方面,我个人认为是领先并影响客户端的。
其中Redux是一个经典案例,并且我觉得Redux官方也挺开放包容的,比如Dan Abramov写的《You Might Not Need Redux》。
虽然我也写过点React,但并没有怎么实践过Redux。
不过这些变化和影响,是我们在解决问题的过程中需要结合考虑的。
实际案例结合
? Redux-like Architecture and Framework
程序 = 数据结构 + 算法。—— Nicklaus Wirth,Pascal之父,图灵奖获得者
这句话乍看起来可能会有点面向过程设计的感觉,但OOP中的对象其实也是由数据+方法组成,而FP则更不用说了。
当只能由Controller 持有-> Model的时候,那么在多复杂卡片的列表场景中,必须由Controller来更新每个View的属性/状态。
-
MyViewController需要为ContactCell更新它的各种相关属性,类似的还有AddressCell、PackageCell等。
-
MyViewController在更新AddressCell展示前,可能还需要先为它计算出合适的富文本展示内容。
-
MyViewController需要响应不同Cell的点击交互行为,包含但不限于按钮点击、输入框变化、富文本跳转、键盘起落等。
-
MyViewController需要响应CollectionView/TableView的DataSource/Delegate各种方法实现。
-
MyViewController需要响应Model层的变更通知,或者是另外一个ViewController抛过来的广播通知。
-
……
然后MyViewController就爆炸了。
针对这种场景,我的解法是:
-
通过让View->Model,基于工厂模式,把组件化Cell基于数据更新的布局逻辑交给View负责,如contactCell.configUIWithModel( contactModel )。这样有点类似上面DDD提到的充血Model,具备高内聚的特点,带来好处:
-
和微软说的减轻控制器负担、推入域模型类似,通过把数据驱动布局的代码推入组件域内,减轻了MyViewController的负担。
-
利于做这部分组件化Cell的UI测试。
-
利于这些组件化视图复用到其它场景,比如交易管理场景的订单卡片可以复用到搜索场景中,不用在SeachViewController里复制粘贴一大堆代码,只需要从Model取一个数据对象丢给组件化Cell即可。
工厂模式下,产品的刷漆、烘干、印花等操作会在内部完成,不会丢一个模型让客户去自己贴logo。MVC的每一部分都可以用不同的设计模式来组合实施,实现解耦或动态灵活的目标。
-
-
基于ViewController,拆分出不同职责的扩展,比如MyViewController+Delegate专门复杂响应代理事件处理。
-
定义出其它类型的Controller,比如MyDataSourceController,专门为TableView提供数据源,可以类比参考Android中ListView的Adapter+ViewHolder。
对应下图:
这里的ViewController/Activity放在哪里,也和上面的一个问题相呼应。
虽然D-KMP主张通过全新构建工程写代码的方式来实践,但从实际情况出发,绝大多数现有系统都会是以单点尝试、渐进式的方式来落地,或发展、或回撤。
那么,如上图所示:
-
如果在既有MVC代码结构中,View -> Model,在这种渐进式迁移场景下,就需要修改View来适配新的ViewModel。
-
如果 View 不引用 Model,则是由 Controller来做胶水层设置更新视图(命令式UI)。
-
后者的好处是,保持View的独立性,只需要修改这个业务场景对应的单个Controller即可。因为View具有可复用性,可能在另外还没准备迁移/改变的模块里也有使用。
所以,此处不建议 View -> Model。
那么,谁对呢p>
? 谁对谁错的务虚讨论
当有程序员要推荐使用其它架构模式的时候,通常开头的一句话就是先说MVC模式的问题。
比如ReSwift在写 Why ReSwift,开头第一句话就是:Model-View-Controller (MVC) is not a holistic application architecture.
我个人认为,
-
架构是一个名词(n.)+动词(v.)。
架构(n. & v.)是为了帮助 开发者在交流时有一致的理解、在业务需要时能够便于扩展、在出问题时能够快速定位等等(对抗熵增)。具体还是看采用这个架构的得失,要解决什么问题或带来什么好处,然后带来什么成本或付出什么代价。
架构(v.)通过分配每部分代码的职责并为他们取名(好比iOS/Android开发工程师这样的岗位名称,让别人一看就知道是做什么的),然后几个名字加在一起 形成了架构模式这个抽象概念。
从具体到抽象,然后再由这个抽象概念去指导程序员实践写代码,促进了架构的传播,比如MVC、MVP、MVVM、MVVM-C、MVI、MV-Whatever,VIPER,Redux and More……
-
有时候并不一定是架构模式的错,还有可能是平台/框架在让架构模式自传播时采用的具体方案出了点问题,又或者是开发者自己写代码的问题。
-
有时候架构模式之间也不是互斥的,也可以在不同场景下互补。比如在MVC里,每个业务表现模块不一定要有三个元素齐聚,甚至也可以 VC-M-VC 共用一个M(可参考斯坦福iOS开发课程内容)。
有一个实际的业务场景是这么实践的:
总体来说,MVVM是基于事件驱动的、以数据绑定机制为支撑的松散解耦架构模式。
-
它的前提是有系统/平台/框架的机制支撑,不然实现成本和复杂度有点大。
-
相比MVC,它的优势是视图控制逻辑不太会膨胀,代码单元更容易被测试,在可读性、可维护性上更好。
-
它可能带来的成本/问题是入门上手较难,简单场景下使用容易过度设计,复杂场景下出问题调试比较麻烦。
考虑到ViewController在该场景的薄胶水特质,以及也参与视图展示和用户响应,所以我把它放在V里。
? 谁对谁错的务实案例:VIPER和分层演变
关于谁对谁错/谁好谁坏,可以再来看一个案例。
-
VIPER架构模式的应用
-
VIPER架构模式的演变
后来随着业务场景的变化,一个业务模块可能需要对接多套 关服务,XxxNetworkInteractor就要做抽象隔离,消除业务逻辑对多套 关请求的感知,并且应用到其它业务模块。
这就有点像 VPER – I – VPER 模式 —— 多个业务模块对 Interactor 进行了复用。
-
退化
再后来,随着技术和业务的变化,底层 关服务ATop成为了领域范围内的事实标准,之前遇到的问题也消失了,而XxxNetworkSDK又带来一些成本,比如要配套ATop的能力升级进行迭代维护。
此时,XxxNetworkSDK可以退出历史舞台了,让业务模块直接面向ATop编程,这样既降低了维护成本,又带来了一定的包大小收益。

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览93065 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!