关于组件化的那些事儿
为什么需要组件化
在 iOS中,苹果官方推荐的 App 开发模式是 MVC,随后衍生出其他很多类 MVC 的设计模式 MVP、MVVM、MVCS ,它们在不同程度上增强了视图、数据的通信方式,使得逻辑、视图、数据之间的通信更灵活、规整、易于扩展。原因在于,MVC 是很好的面向对象编程范式,非常适合个人开发或者小团队开发。
但是,项目大了,人员多了以后,这种架构就扛不住了。当项目因为各种需求,越来越来多时,如果此时的各个模块之间是互相调用,会造成高耦合的情况。一旦我们需要对某一块代码进行修改时,就会牵一发而动全身,导致项目难以维护。
这些问题主要表现在:
- 业务需求不断,业务开发不能停。重新划分模块的工作量越大,成本越高,重构技改需求排上日程的难度也就越大。
- 模块对外接口不明确,甚至暴露了本不该暴露的私有接口。
- 模块间耦合的导致依赖关系混乱,修改起来需要重新梳理逻辑和关系,耗时长。
- 当模块需要重用到其他项目时,难以单独抽离
- 修改某个模块时,会影响到其他模块
当业务需求量和团队规模达到一定程度后,任何一款 App 都需要考虑架构设计的合理性。而谈到架构治理,就需要将老业务、老代码按照新的架构设计模式进行重构。所以,架构重构考虑得越晚,重构起来就越困难,快速迭代的需求开发和漫长的重构之间的矛盾,如同在飞行的飞机上换引擎。及早考虑架构设计就显得尤为重要。
通过组件化,我们可以实现模块间解耦,模块复用的问题,从而提高团队协作开发效率。
模块粒度应该怎么划分
模块划分的原则
首先,项目规模变大后,模块划分必须遵循一定的原则。如果模块划分规则不规范、不清晰,就会导致代码耦合严重的问题.
对于 iOS 这种面向对象编程的开发模式来说,我们应该遵循以下五个原则,即 SOLID 原则。
- 单一功能原则:对象功能要单一,不要在一个对象里添加很多功能。开闭原则:扩展是开放的,修改是封闭的。
- 里氏替换原则:子类对象是可以替代基类对象的。
- 接口隔离原则:接口的用途要单一,不要在一个接口上根据不同入参实现多个功能。
-
依赖反转原则:方法应该依赖抽象,不要依赖实例。iOS 开发就是高层业务方法依赖于协议。
同时遵守这五个原则是开发出容易维护和扩展的架构的基础。
合适的模块粒度
组件可以认为是可组装的、独立的业务单元,具有高内聚,低耦合的特性,是一种比较适中的粒度。先按照物理划分,也就是将多个相同功能的类移动到同一个文件夹下,然后做成 CocoaPods 的包进行管理。
但是,仅做到这一步还不够,因为功能模块之间的耦合还是没有被解除。如果没有解除耦合关系的话,不同功能的开发还是没法独立开来,勉强开发完成后的影响范围评估也难以确定。接下来,就需要重新梳理组件之间的逻辑关系,进行改造。
组件化分层
组件解耦并不是说要求每个组件间都没有耦合,组件间也需要有上下层依赖的关系。组件间的上下层关系划分清楚了,就会容易维护和管理。关于组件化的分层,可以参考下图:
组件化分层
- 基础组件,与业务无关的,比如网络、存储、宏、分类等等
- 通用组件,常用控件,数据管理,分享等等
- 业务组件,迭代业务,更新频率最高
组件解耦并不是说要求每个组件间都没有耦合,只是说避免组件间的横向依赖,组件间也需要有上下层依赖的关系。组件间的上下层关系划分清楚了,就会容易维护和管理。
组件间通讯
那么是如何避免组件间的横向依赖的呢?在实践中,一般分为了协议式和中间者两种架构设计方案。
Protocol-Class
协议式架构设计主要采用的是协议式编程的思路:在编译层面使用协议定义规范,实现可在不同地方,从而达到分布管理和维护组件的目的。这种方式也遵循了依赖反转原则,是一种很好的面向对象编程的实践。
protocol-class
通过在内部维护了一个 protocol
和 class
的映射表,根据对于的 protocol
来获取对应的 class
,使用方不需要在意 class
是哪个类,只要知道它实现了 protocol
就可以。
Mediator
另一种常用的架构形式是中间者架构。它采用中间者统一管理的方式,来控制 App 的整个生命周期中组件间的调用关系。同时,iOS 对于组件接口的设计也需要保持一致性,方便中间者统一调用。常见的用法有:target-action
、UrlRouter
.
-
target-action
利用一个中间件来调用,中间件利用runtime
来调用其他组件,这种方式可以做到真正意义上的解耦。然后再通过实现中间件category
的方式来提供服务,使得使用者只需要依赖中间件,而组件不需要依赖中间件。例如:CTMediator
-
UrlRouter
UrlRouter
基于 URL 匹配的,或者是根据命名约定,用runtime
方法进行动态调用
例如:MGJRouter 、HHRouter