前言
现在很多公司在业务扩张到一定程度或者商业模式跑通之后就会考虑到组件化方案,而组件化方案目前主流的做法就是以下三种,这里我们将就组件化方案谈谈自己的思考。
URL Scheme
Protocol Class
Target Action
URL Scheme
方案
实现方式:
在启动时,注册组件提供的服务(注册URL以及关联服务Block),然后在使用时,通过URL直接调用(openURL);
- 使用URL处理本地跳转逻辑
- 注册组件,内存中维护URL注册服务
优点:
- 统一了不同平台的客户端在跳转上的不一致
- 处理网页跳转上较为方便
缺点:
- url参数受限(例如非字符串类型,UIImage,XXModel,NSData等)
- 业务分散,耦合多
- 组件很多的情况下,可能会影响到内存
Protocol Class
方案
实现方式:
通过Protocol定义服务接口,组件通过实现该接口来提供服务,最终的实现就是将Protocol和Class映射在一起,同时在内存中保存一张映射表,使用的时候,通过Protocol去获取Class对应的服务。
- 增加 Protocol Wrapper(包装) 层
- 中间件返回 Protocol 对应的 Class
- 解决硬编码的问题
优点:
- 协议接口规范,遵循依赖反转原则
缺点:
- 缺少统一调度层,组件方法调用分散,难于集中管理(团队规模大,架构管控越重要)
- 架构的灵活性不够高
Target Action
方案
实现方式:
Target Action这个方案是基于ObjC
的runtime
、category
特性动态获取模块,例如通过NSClassFromString
获取类并创建实例,通过 performSelector
+ NSInvocation
动态调用方法。
首先每个模块需要配置Target和Category,其中Target是每个组件对应一个或者多个Target,Category是中间层Mediator的分类,使用分类的目的是为了让Mediator的业务代码分离,从而降低Mediator中的依赖和耦合性。
那么中间层Mediator是如何找到并调用组件的呢?这里正是利用了runtime的反射机制,在Category中找到对应Target以及调用Target对应的Action。
CTMediator正是采用的Target Action
方案,巧妙的使用了cocoaTouch提供的反射机制,方法签名与命令模式,简单又完美的解决了组件间的解耦问题。
- 抽离业务层逻辑
- 提供由中间层调用逻辑
- 中间层实现上使用Runtime反射
优点:
- 解耦,只存在组件依赖中间层(单向依赖)
- 利用 Category 可以明确声明的接口,进行编译检查
- 统一处理了所有组件间调用入口,方便管理
缺点:
- 每个组件的Category对应一个Target,Category中的Action对应Target中的Action。(此类的代码量很多)
- 有硬编码的问题,各种定义的string
组件化的思考
前面写到组件化方案的各个差异,那么来谈谈我自己对组件化架构的看法。
首先组件化的最大目的就是解耦合,易扩展。首先我们需要考虑的应该是如何将模块直接解耦,如何将模块细分。模块划分的原则要基于什么,这个要结合业务去拆分。
然后我们再次基础上搭建出容易维护和扩展的架构,即遵循SOLID
以下五个原则:
- 单一功能原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖反转原则
最后将拆分好的模块通过cocoapod
打包成库进行管理。
等等,这就结束了吗?当然还没完呢,在这里组件和组件之间的调用还是没有解耦,所以需要我们的组件化架构出场了,我们需要理清各个组件之间的依赖关系以及调用逻辑,划分层次。组件间的分层可以分为:
- 底层(基础组件)
- 中间层(通用的业务组件)
- 业务层
目前组件化的架构上主要分为协议式Protocol-Class
(蘑菇街方案)以及中间者Target-Action
(CTMediator) 两种,在我看来前者可能适合侧重点在HTML上业务更多的团队。协议式最大的诟病就是缺乏统一的管理。而后者的优点体现在架构上容易管控,易扩展,所以更为推荐使用后者。
当然最终如何选择架构,目的都是使业务解耦,使得项目更容易维护以及扩展新业务。很多人会追求完美主义,追求理想中的解耦。在我看来组件化的重点是业务层的解耦,组件化的基础是有一个好的基建。