框架设计模式-⑦组件化

[toc]

一、概念了解

1、组件化

就是“基础库”或者“基础组件”,意思是把代码重复的部分提炼出一个个组件供给功能使用。

​ 使用:Dialog,各种自定义的UI控件、能在项目或者不同项目重复应用的代码等等。

​ 目的:复用,解耦。

​ 依赖:组件之间低依赖,比较独立。

​ 架构定位:纵向分层(位于架构底层,被其他层所依赖)。

2、模块化

就是“业务框架”或者“业务模块”,也可以理解为“框架”,意思是把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。

​ 使用:按照项目功能需求划分成不同类型的业务框架(例如:注册、登录、外卖、直播…..)

​ 目的:隔离/封装 (高内聚)。

​ 依赖:模块之间有依赖的关系,可通过路由器进行模块之间的耦合问题。

​ 架构定位:横向分块(位于架构业务框架层)。

3、总结

​ 其实组件相当于,把一些能在项目里或者不同类型项目中可复用的代码进行工具性的封装。

​ 而模块相应于业务逻辑模块,把同一类型项目里的功能逻辑进行进行需求性的封装。

二、组件化的必要性/产生背景

1、产生背景:

所有模块代码都编写在一个项目中,在项目越来越大后,测试/使用某个模块或功能,需要编译运行整个项目,麻烦

2、组件化思路:

将每个模块作为一个组件,加一个中间层来协调各个模块间的调用,所有的模块间的调用都会经过中间层中转。只让其他模块对中间层产生耦合关系,中间层不对其他模块发生耦合。)

3、组件化好处:

业务划分更佳清晰,新人接手更佳容易,可以按组件分配开发任务。

项目可维护性更强,提高开发效率。

更好排查问题,某个组件出现问题,直接对组件进行处理。

开发测试过程中,可以只编译自己那部分代码,不需要编译整个项目代码。

方便集成,项目需要哪个模块直接通过CocoaPods集成即可。

三、组件化设计思路1:有管理中心

完整的项目:https://github.com/dvlproad/033-Data-Notification-iOS

1、核心思想

启动时候注册关系表,执行时候遍历关系表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 核心思想1:注册,以统一管理,后续调用时候,从注册表中查询并执行对应的操作
void register(url, handle) {
model.url = url;
model.handle = handle;
models.add(model); // 问题1:用什么管理所有数据
}

// 核心思想2:执行,遍历在注册表中的所有操作,找到对应的操作后进行执行
void exec(url) {
for (var model in models) {
if (model.url == url) {
model.handle();
break;
}
}
}

2、实现方案

2.1、方案1:Url+block 路由器

缺点:入参,取参不明显。

2.2、方法2:protocal - class

将要执行的方法统一放到类中,通过protocal或者类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据 protocal 查找 class
// 方法1(可一对多):为 protocal 添加监听者数组属性(通过runtime,形成类似于 protocal.listeners ),
NSHashTable *protocalListeners = objc_getAssociatedObject(protocol, CJ_BROADCAST_PROTOCOL_LISTENER); // 找到之前绑定到这个protocal上的监听者列表。
objc_setAssociatedObject(protocol, CJ_BROADCAST_PROTOCOL_LISTENER, protocalListeners, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

注册的时候 [protocalListeners addObject:listener];

// 方法2(只能一对一):以 protocal 为 key , 通过 protocal 存取 module 模块(模块自身遵循协议)
// 创建一个NSMapTable实例,其中键是强引用,值是弱引用
NSMapTable<NSString *, NSObject *> *weakRefHashTable = [NSMapTable strongToStrongObjectsMapTable];

// 向哈希表中添加一个键值对
NSString *key = @"someKey";
NSObject *value = [[NSObject alloc] init];
[weakRefHashTable setObject:value forKey:key];

模块协议拆分为多个子协议,用户

网络处理协议、数据管理协议(单独)、用户界面更新协议

改进思想

1
2
3
4
5
6
7
8
9
10
// 1、发起请求(LoginViewController)
id<OrderModulePublic> moduler = [[CJMoudleManager shared] modulerForProtocal:@protocal(OrderModulePublic)];
UIViewController *vc = [moduler orderViewControllerWithPageType:xxx];

// 2、实现请求(TSOrderModuleImpl)
[CJProtocolCenter addModule:self forProtocol:@protocol(OrderModulePublic)];
#pragma mark - OrderModulePublic
- (UIViewController *)orderViewControllerWithPageType:(int)pageType

}

上述代码还可以演变成实现通知的一种形式(当注入的类中,有多个类都遵循了(实际上不应该出现)模块协议时候)

1
2
3
4
5
6
7
8
9
10
11
12
// 1、发起请求(LoginViewController)
NSMutableSet<id<CJUserServiceProtocolForModule>> listener = [[CJMoudleManager shared] listenersForProtocal:@protocal(CJUserServiceProtocolForModule)];
for (id<CJUserServiceProtocolForModule> listener in listeners) {
[listener userNameUpadte:userName];
}

// 2、实现请求(OrderHomeViewController)
[CJProtocolCenter addModule:self forProtocol:@protocol(CJUserServiceProtocolForModule)];
#pragma mark - CJUserServiceProtocolForModule
- (void)userNameUpadte:(NSString *)userName

}

下列图片来源于 《架构分层.graffle》 中的【三、模块间设计】

跳转的使用示例:

modular_CJProtocalManager 跳转

通知的实现示例:

modular_CJProtocalManager 通知

跳转 + 数据变化后内部通知的实现示例:

modular_CJProtocalManager 跳转 + 数据变化后内部通知

四、组件化设计思路2:无管理中心

项目示例:CJStandardProject 中 CTMediator+CJDemoModuleMain.h

1、核心思想

核心思想:直接执行,让系统通过反射来找到要响应事件的类和方法

1
2
3
void exec(target, action, params) {

}

2、实现方案

2.1、方案1:CTMediator target+action+param

实现示例:

modular_CTMediator target+action+param

2.2、方案2:XXXService+Implemention

modular_XXXService+Implemention

五、组件化实施过程的思考

1、架构分层

整个APP架构上从上到下分为三层,独立于APP的通用层,通用业务层,业务层。

架构分层1

1.1、独立于APP的通用层

此层常为:一些Cocoapods公有库或者自己编写的独立于APP的库。

举例:MJExtension、CJNetwork、分享、CJOverlayKit(Toast、HUD、ActionSheet)、CJShareList

1.2、每个APP自己的通用层

此层常为:前面的独立于APP的通用层的二次封装。若公司内部引用了第三方库,按照依赖倒置的原则,建议封装一层之后放到Basic Specs供业务方使用。好处:跟外部环境有效隔离,第三方库发生问题,公司内部可控。

如:CQNetwork、CQOverlayKit、CQShareKit、网络库CQNetwork、数据库(FMDB/WCDB)、缓存库等

依赖倒置原则告诉我们要面向接口编程;通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。

1.3、业务模块

业务层的模块应该按照模块化的设计思想,尽量做到高度的“高内聚,低耦合”。

因模块高度独立,且高频使用,若公司内部有多个App同时需要依赖,建议单独创建私有库Specs。

iOS端APP架构设计心得

END