iOS中的依赖性太多?使用组合根模式
iOS架构不仅仅是MVC、MVVM和M*。
如果你问一个iOS工程师,"你将使用哪种架构来设计X应用程序?",他们通常会回答MVC、MVVM、MVP等等。
这篇文章将解释为什么这些流行的缩略语不能完全回答这个问题,并将展示一个替代的解决方案。
MVC、MVVM、MVP是UI层设计或UI架构,而不是系统架构。
它们描述了数据的流动和UI层内的责任分离。它并没有回答任何关于导航、网络、缓存、业务逻辑等问题。
将这些责任添加到UI层中会产生具有大量依赖关系图的单体应用。这导致了重写、无法测试的代码和高成本的变化。
解决办法是将单体分解成模块化的组件,并在 "组合根**"**处将它们组合在一起。例如,你想创建一个类似于Instagram的图片feed应用。
一个常见的用户界面架构可能是这样的。
用户选择一个FeedImageCell ,并期望导航到一个FeedDetailedViewController 。
上面的代码是一个常见的策略,要求视图控制器负责导航和创建其子视图控制器。
现在想象一下,你的需求发生了变化,你现在需要实现记录、从远程或本地资源库检索资源以及支持旧操作系统的能力。
你使用URLSession ,在viewDidLoad ,如下图所示,加载数据。
当用户选择一个项目时,你检查以确定导航到哪个控制器并记录它。
这些微小的变化需要开发人员修改FeedViewController 。这违反了Open/Close原则,因为可能的导航路线在编译时就已经很早确定。
即使我们通过构造注入将依赖关系注入到延迟行为的FeedViewController ,我们仍然需要注入它创建的任何子视图控制器的依赖关系。
如果子视图控制器有子视图控制器,我们就必须在根视图控制器上注入这些责任,并将它们层叠下去。这可能会在大型复杂的应用程序中无限地持续下去,并在大规模的依赖关系图中产生重大问题。
通过与LoggingFramework 和URLSession 耦合,你已经可以看到围绕FeedViewController 的依赖网络。它的任何子节点的依赖性也将成为它的依赖性。
迎接组合根 模式
组成根模式延迟了关于组件如何交互的决策,并允许我们拦截和修改行为。
它存在于Main 模块中,这个概念对大多数iOS工程师来说是陌生的。
让我们来看看如何在主模块中组合组件并打破单体。
现在,FeedViewController 不再负责创建和导航它的孩子。
FeedComposer 组合了FeedViewController ,并将其与所有的依赖关系组装起来。使用闭包,我们可以注入行为,而无需将UI模块组件与Networking 或Logging 模块耦合。
FeedNavigation 处理路由,Logging 模块通过其委托被通知。
注意依赖关系的流程。主模块依赖于所有其他模块,而没有一个模块知道彼此的情况。
通过创建模块化代码并在组合根部将组件组合在一起,我们在模块之间创造了虚拟边界。
假设我们希望我们的Feed应用程序能够在Apple Watch或Mac OS上运行。最初的方法是在UI层中耦合所有的东西,并称其为MVC/MVVM/M*,这需要完全重写。
用我们的新方法,我们只需要重写FeedUI 模块,并在Main中的组成根部连接组件。所有的业务逻辑、网络、日志都将被重新使用。这就是模块化设计的力量。
谢谢你的阅读。完整的源代码可以在GitHub仓库中找到。