浅谈软件开发架构模式

软件开发活动是整个流程的核心环节:接收产品和视觉设计需求/变更作为输入,然后输出客户可用的终端产品。

而统一的软件开发架构模式,则是我们保障软件开发质量的基础。(这里就不具体展开WHY了)

由于讨论的是具体面向客户使用的业务场景,少不了客户操作交互的视图层(View),所以我从MVC开始谈起。

从表现层的MVC谈起

我之所以没有在上图中对M-V-C添加箭头线条,是因为在这一点上,不同程序员也有不同理解和实践。(2)

这是第一个需要明确的点:MVC架构模式在多层系统架构里的应用范围。

左侧 业务表现层-业务服务层-基础服务层 是移动端三层架构模式,未涉及到 C/S 交互;右侧是Web B/S场景的三层架构模式。

因为有些应用会比较简单,根本不需要业务服务或基础服务层,纯粹靠一个MVC(或者VC)就能交付出一个Mobile/Web App;而且在一些业务系统里,Web前端/桌面客户端/移动App 也可能会被简化为 大前端/大终端表现层;所以可能基于不同信息,不同程序员对此会有不同认知。

但随着用户终端应用的重要性和复杂度的提升,已经从简单应用发展到复杂多团队协同的平台型或航母级应用,仅靠一个MVC来完成交付是不合适的。

也可以反过来想,程序员会把以下代码放在客户端代码的哪一层:

  1. 对Web引擎的扩展逻辑。

  2. 通信协议的结构定义,以及相应的socket连接和通信代码。

  3. 一个业务相关且UI无关的平台开放能力。

  4. Crash捕获、卡顿监控、日志埋点等功能实现,比如Android在做APM相关事情时会采用AOP方式,利用ASM、AspectJ等方案来做字节码插桩。

  5. ……

?  业界基于MVC模式的不同实践

前面提到不同程序员对MVC模式的理解和实践存在差异,业界大厂亦然,比如苹果、微软,在自身实践MVC模式或者向开发者提供相关文档时,也一样有不同的说法。

  • 微软

可以看出微软在MVC的实践上:

  1. Controller可以引用View和Model。

  2. View可以引用Model。

  3. 这里的Model倾向于是Passive。

值得注意的是,微软在文档里写到:

  1. 强类型视图通常使用 ViewModel 类型,旨在包含要在该视图上显示的数据。 控制器从模型创建并填充 ViewModel 实例。

  2. 控制器不应由于责任过多而变得过于复杂。 要阻止控制器逻辑变得过于复杂,请将业务逻辑推出控制器并推入域模型。

  • 苹果 & 斯坦福

和上面微软、苹果给的图,又不一样了 *_*

  • Google

谷歌相关的描述则是:

问题一:如何解决MVC中Controller的膨胀臃肿问题p>

要回答如何解决,需要先思考为什么膨胀。

问题二:View能否引用Modelp>

  1. 要回答能否引用,需要先定义引用关系是什么。

    是持有对象,还是调用CURD接口操作对象。

    又或者这两者没有必要区分,因为持有的对象本身就可能带CURD接口。

  2. 参考上面相关资料,目前(1)微软2021年文档支持可以引用;(2)苹果/斯坦福2011年文档反对。

  3. 在DinamicX研发模式中,模板View持有data数据对象在内部进行填充和渲染。

问题三:存在View -> Model,那么是否可以反过来存在 Model -> Viewp>

和问题二在描述上相反但又有关联,如果对问题再进一步提问的话:

  1. 使用 -> 引用关系,是为了解决什么问题p>

  2. 使用 -> 引用关系,会产生什么问题p>

如同文章开头所说,以上问题需要结合具体场景来展开(见 实际案例结合),尽量从务虚到务实。

?  Redux-like Architecture and Framework

随着前后端分离得更彻底,终端设备性能和用户体验重要性的提升,前端领域也得到了蓬勃发展,开发方式也有了比较大的变化,MVC-like方式不再是主流:

  1. UI开发方面从早期的命令式到现在的声明式。

  2. 整体应用和业务逻辑实现方面,从早期的OOP写法,转向基于FP的响应式编程。比如Redux的数据流、React的Hook特性等。

  3. 各种框架蓬勃发展,一些概念和模式的提出、实践应用方面,我个人认为是领先并影响客户端的。

其中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的属性/状态。

  1. MyViewController需要为ContactCell更新它的各种相关属性,类似的还有AddressCell、PackageCell等。

  2. MyViewController在更新AddressCell展示前,可能还需要先为它计算出合适的富文本展示内容。

  3. MyViewController需要响应不同Cell的点击交互行为,包含但不限于按钮点击、输入框变化、富文本跳转、键盘起落等。

  4. MyViewController需要响应CollectionView/TableView的DataSource/Delegate各种方法实现。

  5. MyViewController需要响应Model层的变更通知,或者是另外一个ViewController抛过来的广播通知。

  6. ……

然后MyViewController就爆炸了。

针对这种场景,我的解法是:

  1. 通过让View->Model,基于工厂模式,把组件化Cell基于数据更新的布局逻辑交给View负责,如contactCell.configUIWithModel( contactModel )。这样有点类似上面DDD提到的充血Model,具备高内聚的特点,带来好处:

    1. 和微软说的减轻控制器负担、推入域模型类似,通过把数据驱动布局的代码推入组件域内,减轻了MyViewController的负担。

    2. 利于做这部分组件化Cell的UI测试。

    3. 利于这些组件化视图复用到其它场景,比如交易管理场景的订单卡片可以复用到搜索场景中,不用在SeachViewController里复制粘贴一大堆代码,只需要从Model取一个数据对象丢给组件化Cell即可。

      工厂模式下,产品的刷漆、烘干、印花等操作会在内部完成,不会丢一个模型让客户去自己贴logo。MVC的每一部分都可以用不同的设计模式来组合实施,实现解耦或动态灵活的目标。

  2. 基于ViewController,拆分出不同职责的扩展,比如MyViewController+Delegate专门复杂响应代理事件处理。

  3. 定义出其它类型的Controller,比如MyDataSourceController,专门为TableView提供数据源,可以类比参考Android中ListView的Adapter+ViewHolder。

对应下图:

这里的ViewController/Activity放在哪里,也和上面的一个问题相呼应。

虽然D-KMP主张通过全新构建工程写代码的方式来实践,但从实际情况出发,绝大多数现有系统都会是以单点尝试、渐进式的方式来落地,或发展、或回撤。

那么,如上图所示:

  1. 如果在既有MVC代码结构中,View -> Model,在这种渐进式迁移场景下,就需要修改View来适配新的ViewModel。

  2. 如果 View 不引用 Model,则是由 Controller来做胶水层设置更新视图(命令式UI)。

  3. 后者的好处是,保持View的独立性,只需要修改这个业务场景对应的单个Controller即可。因为View具有可复用性,可能在另外还没准备迁移/改变的模块里也有使用。

所以,此处不建议 View -> Model。

那么,谁对呢p>

?  谁对谁错的务虚讨论

当有程序员要推荐使用其它架构模式的时候,通常开头的一句话就是先说MVC模式的问题。

比如ReSwift在写 Why ReSwift,开头第一句话就是:Model-View-Controller (MVC) is not a holistic application architecture.

我个人认为,

  1. 架构是一个名词(n.)+动词(v.)。

    架构(n. & v.)是为了帮助 开发者在交流时有一致的理解、在业务需要时能够便于扩展、在出问题时能够快速定位等等(对抗熵增)。具体还是看采用这个架构的得失,要解决什么问题或带来什么好处,然后带来什么成本或付出什么代价。

    架构(v.)通过分配每部分代码的职责并为他们取名(好比iOS/Android开发工程师这样的岗位名称,让别人一看就知道是做什么的),然后几个名字加在一起 形成了架构模式这个抽象概念。

    从具体到抽象,然后再由这个抽象概念去指导程序员实践写代码,促进了架构的传播,比如MVC、MVP、MVVM、MVVM-C、MVI、MV-Whatever,VIPER,Redux and More……

  2. 有时候并不一定是架构模式的错,还有可能是平台/框架在让架构模式自传播时采用的具体方案出了点问题,又或者是开发者自己写代码的问题。

  3. 有时候架构模式之间也不是互斥的,也可以在不同场景下互补。比如在MVC里,每个业务表现模块不一定要有三个元素齐聚,甚至也可以 VC-M-VC 共用一个M(可参考斯坦福iOS开发课程内容)。

有一个实际的业务场景是这么实践的:

总体来说,MVVM是基于事件驱动的、以数据绑定机制为支撑的松散解耦架构模式。

  1. 它的前提是有系统/平台/框架的机制支撑,不然实现成本和复杂度有点大。

  2. 相比MVC,它的优势是视图控制逻辑不太会膨胀,代码单元更容易被测试,在可读性、可维护性上更好。

  3. 它可能带来的成本/问题是入门上手较难,简单场景下使用容易过度设计,复杂场景下出问题调试比较麻烦。

考虑到ViewController在该场景的薄胶水特质,以及也参与视图展示和用户响应,所以我把它放在V里。

?  谁对谁错的务实案例:VIPER和分层演变

关于谁对谁错/谁好谁坏,可以再来看一个案例。

  • VIPER架构模式的应用

  • VIPER架构模式的演变

后来随着业务场景的变化,一个业务模块可能需要对接多套 关服务,XxxNetworkInteractor就要做抽象隔离,消除业务逻辑对多套 关请求的感知,并且应用到其它业务模块。

这就有点像 VPER – I – VPER 模式 —— 多个业务模块对 Interactor 进行了复用。

  • 退化

再后来,随着技术和业务的变化,底层 关服务ATop成为了领域范围内的事实标准,之前遇到的问题也消失了,而XxxNetworkSDK又带来一些成本,比如要配套ATop的能力升级进行迭代维护。

此时,XxxNetworkSDK可以退出历史舞台了,让业务模块直接面向ATop编程,这样既降低了维护成本,又带来了一定的包大小收益。

568862d6c8e399878d8f4c1623939804.png

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览93065 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年1月9日
下一篇 2022年1月9日

相关推荐