Arclin

Advocate Technology. Enjoy Technology.

0%

组件化方案之强业务组件的设计

本文简述组件化方案之强业务组件的设计

如何定义强业务组件

涉及到具体业务需求,能单独完成App某个功能点(主要feature)的组件

需要符合什么要求

UI层和逻辑层的绝对独立

需求内容完整

可拓展,可维护

可独立配置UI(UI只可重写、不可修改)

逻辑层不可配置

代码内容清晰明确

接下来开始讲故事

背景

随着业务发展,灵机大师说的咨询室模块需要抽离到各个App当中使用,但是由于不同App的网关配置和UI配置都不一样,造成了代码不可以完全进行复用,所以需要对原有代码重新设计。

基本思想

如何设计组件是个比较复杂的问题

首先参考其他第三方IM提供商的Demo,基本上要用他们的UI的话就只能全套用,提供的配置项比较有限,自己改里面的东西成本很大,也不方便。

然后考虑自己如何修改原有代码

参考原有MVVM架构的话,在View和ViewModel之间是一个强耦合的状态,View会直接依赖ViewModel,如果使用方需要定制UI则会十分懵逼,不明确自己的View需要做什么才能配合ViewModel的使用。但这并不意味着MVVM就不能用,在组件初期,为了快速成型,必须沿用之前的设计模式。

接下来思考第二种方案,一种能够高度分离职责的架构,那便是VIPER。确实,在VIPER的设计思想中,单一职责原则体现得很好:Presenter寻找实现了InputProtocol的ViewController并给予数据,ViewController内的操作事件则通过实现了OutputProtocol的Presenter去执行,Presenter内部又通过Interactor和wireframe去实现数据获取和路由的交互,这种面相协议的开发也十分契合我们的需求,协议的方式能够很明确的让使用者知道他要做什么和他需要给予什么。但是VIPER的结构比较复杂比较适合后期在发展,目前还是以MVVM为主比较好。

那么如何改进我们现有的MVVM模式?这里我们就要将VIPER的精髓,面向协议和依赖注入抽离出来。

依赖注入

首先先说明什么叫做依赖注入

比如AController跳转到BController,那么这时候BController就需要在AController内部进行实例化,如下

1
2
3
4
5
6
7
8
9
10
11
12
@implementation AController : UIViewController

...

- (void)jump
{
BController *bController = [[BController alloc] init];
[self.navigationController pushViewController:bController animated:YES];
}

@end

这么做的话,当AController被封装成组件之后,BController的配置将会被限制,外部无法改变BController任何细节,所以我们 ** 稍 加 改 进 **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation AController : UIViewController

...

- (instancetype)initWithCreateBlock:(UIViewController *(^)(void))createBViewControllerBlock {
....
self.createBViewControllerBlock = createBViewControllerBlock;
...
}

- (void)jump
{
UIViewController *bController = self.createBViewControllerBlock();
[self.navigationController pushViewController:bController animated:YES];
}

@end

1
2
3
4
[[AController alloc] initWithCreateBlock:UIViewController* ^{
BController *bController = [[BController alloc] initWithTitle:@"xxx"];
return bController;
}];

将BController的创建通过Block暴露出来,AController内部不关心BController是如何被创建的,那么AController对BController的依赖将通过外部的Block进行注入。

这,就是依赖注入。

当然这是最简单的依赖注入,无法满足我们复杂的需求,所以有时候我们需要使用第三方框架,如ObjectionTyphoon

依赖倒置

依赖倒置是是六个设计原则之一。依赖倒置意味着实现依赖于抽象,抽象不依赖实现。

举个例子

当你需要买一杯饮料,你不需要到具体哪家店买,反正任意一个便利店都有饮料卖,所以你需要的并不是获得美宜佳的地址或者711的地址,你需要的只是一个“卖饮料的店”,这是一个抽象的概念,这样子就是依赖被倒置了,本来是你去寻找美宜佳,现在变成拥有“卖饮料”功能的店去满足你。

在iOS中,抽象是通过代理体现的

同样的,我们再来看看刚刚的代码

1
2
3
4
5
6
7
8
9
10
11
@implementation AController : UIViewController

...

- (void)jump
{
BController *bController = [[BController alloc] init];
[self.navigationController pushViewController:bController animated:YES];
}

@end

根据依赖倒置原则进行改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

@interface AController()

@property(weak) id<BControllerProtocol> bViewController;

@end

@implementation AController : UIViewController

...

- (void)jump
{
[self.navigationController pushViewController:self.bViewController animated:YES];
}

@end
1
2
3
AViewController *aViewController = [[AViewController alloc] init];
id<BControllerProtocol> bViewController = [[BViewController alloc] init];
aViewController.bViewController = bViewController;

依赖注入+依赖倒置

将协议和注入的模式进行结合,仿照上面的代码,估计就是这样子

1
2
3
4
[[AController alloc] initWithCreateBlock:UIViewController<BControllerProtocol>* ^{
id<BControllerProtocol> bController = [[BController alloc] initWithTitle:@"xxx"];
return bController;
}];

当然 这种模式只是个例子,给大家一种具体的观感

实际上在编码的时候,一个控制器往往有很多依赖,这里分为可注入依赖不可注入依赖

可注入的依赖往往是一些可以配置的依赖,使用者通过将自己自定义的配置注入,覆盖原有的默认依赖,达到配置内部样式的效果

反之,不可注入的依赖就会是代码中固定的耦合项目,合理的耦合以不至于组件产生过多未知问题。

刚刚说到一些依赖注入的第三方框架ObjectionTyphoon,这个之后再详细介绍,这里简单说明一下,当使用方需要使用自定义类注入组件的时候,都是需要依赖这两种框架,不能只由组件内部依赖,所以为了避免不必要的学习成本,暂时先不使用,后面如果拓展开了使用方法,可能会修改使用,我们这里先用一些理解起来比较简单但是操作起来可能比较麻烦的注入方式。

首先我们这里我们新增一个单例用于注入,通过反射机制实例化自定义的对象,然后进行注入。

当然这个对象是需要遵循我们对应的协议的。

协议我们按照VIPIER的规范,分为两种InputProtocolOutputProtocol

InputProtocol包括控制器需要的外部参数
OutputProtocol包括控制器的回调参数

按照目前咨询室的设计模式看来,是由三个控制器(一个父控制器和两个子控制器)构成,他们分别会有自己的InputProtocolOutputProtocol,如果业务端需要自己实现UI则自己实现协议然后进行注入即可。

待续

有空继续写