关于App项目的组件化漫谈(二)

首先要规划项目的整体架构:

      项目的整体架构并不是所谓的MVC还是MVVM,在我看来,这些只是代码组织的方式,严格意义上来说并不属于项目架构,项目架构需要你站在更高的维度去看事情,规划项目如何去分层;其实一个项目的架构设计与产品的设计仅仅相关,业务层的代码划分为业务层,第三方库属于通用层,我们可以根据业务层对代码的依赖层度来划分,理所当然业务层就应该在最上面,通用层的代码在最下面,如图:

图中又多出来两次,中间层和通用业务层,通用业务层就是可以分别给业务层使用的业务编码;中间层的作用就是协调和解耦的作用,协调组件间的通信,解除组件间的耦合,它要做的也就是我们常提到的组件通信方案;

  然后就来谈一下基础组件的管理。基础组件大概有下面几种:

 一、项目中可能会依赖很多第三方开源库,比如AFNetworking、SDWebImage等,这些都是开源框架上的项目,它们都是对系统API进行封装,并不依赖于业务,我们可以把它们归到基础组件中,推荐使用cocoapod来管理这些库

二、在一些比较大的公司或对要求比较高的公司往往会对一些三方库进行二次开发,来满足一些特殊需求或者弥补一些缺陷,那么这些二次封装的库也可以放到通用层,推荐使用本地私有库,利用cocoapod进行管理

三、在开发业务时,我们也可以从业务代码中抽取一些可以通用的东西,比如自定义弹框、滑动效果图等等,都可以抽离成单独的框架。

在整理这些基础组件的过程中,势必要改很多业务层的代码,这会让人很烦,但是做这些事情的同时也是在为我们的业务组件化铺路,也就是说,抽取基础组件会推进我们做业务组件化。

最后就是所谓的业务组件化了:

既然我们封装的基础组件可以使用私有pod库来管理,业务层的代码同样也可以使用私有pod进行管理,如果项目的业务划分明确,每条业务线都可以单独抽离成私有pod库来管理,这样各条业务线同时开发互不影响,感觉结构上和代码上都清晰了很多。

但是如果业务A调用业务B的代码怎么办?之所以抽离组件化就是为了解耦合,况且私有pod库也不允许我们这样做,因为校验私有库repo的时候,这样的做法根本通不过,为了解决这个问题,所以我们才引入了中间层,这就是中间层的价值,也是本文要讲的组件间的通信方案。

 

iOS端通用的组件间通信方案有如下3种:

1、URL Router 典型代表MGJRouter

2、Target=Action典型代表CTMeditor和阿里的BeeHive种的Router

3、面向接口编程典型代表就是Objection(很强大哦)

URL Router这种方案的优点是能解决组件间的依赖,并且方案成熟,有很多知名公司都在用;缺点是编译阶段无法发现潜在bug,并且需要去注册&维护路由表;

Target-Action是通过OC的反射机制拿到最终的类和需要调用的方法,我们可以实现各种灵活的解耦,将任意类实例化过程封装到任意一个Target中,相比前者,也不需要注册和内存的占用,但是缺点是,编译阶段无法发现潜在的bug,而且开发者需要严格遵循Target-Action的命名规范,调用者可能会因为硬编码问题导致调用失败;

 

面向接口编程;在Java中,接口是Interface,在OC中,接口是Protocol,所以在OC中面向接口编程又叫面向协议编程;举个最通俗易懂的例子来说明面向接口编程:

比如要开发一款模仿狗叫的软件,首先是初始化一只狗,然后再实现一个狗叫的方法即可;是不是;如果业务越做越大,有一天产品经理让你再做一款模仿猫叫的功能怎么办,到了后期又让你做一款模仿老虎叫的功能该怎么办?估计你的心情此刻要崩溃了;现在用面向接口编程的思想对业务进行改造;只需要定义一个协议Protocol,只需要实现一个通用的叫的方法,都可以做这个需求;此时你初始化对象的时候,只需要这样

id <AnimalVoiceProtocol> dog = [[Dog alloc] init];

[dog voice];

id <AnimalVoiceProtocol> cat = [[Cat alloc] init]

[cat voice];

现在我们只需要把这些叫的事件封装到一个Task任务中,就搞定了整个需求

@interface Task : NSObject

@property(nonatomic, strong) id <AnimalVoiceProtocol> animal;

 

- (instance)initWithAnimal:( id <AnimalVoiceProtocol>)animal;

- (void)handle:

@end 

 

@implementation Task

- (void)handle{

    if([_animal respondsToSelector:@selector(voice)])

    [_animal voice];

}

@end

大家可以看到,task没有依赖任何的动物类,而是直接依赖接口AnimalVoiceProtocol;

首先接口比对象更直观,调用者只需要关心接口就行;依赖接口而不是依赖对象,刚才我们使用面向接口编程的方式创建对象

id <AnimalVoiceProtocol> dog = [[Dog alloc] init]; 现在我们除了引用AnimalVoiceProtocol类,还要引用Dog类,一下子引用了2个,好像把问题复杂化了,所以我们想办法只引用AnimalVoiceProtocol而不引用类,这个时候就需要把Protocol和这个Protocol的具体实现类绑定在一起(Protocol-class),当我们通过Protocol获取对象的时候,实际上获取的是遵守了和这个Protocol协议的对象,当然了,有时候一个Protocol对应多个实现类怎么办,别忘了还有工厂模式哦;

所以我们需要把协议和class绑定到一起

[self bindBlock:^(id objc){

    Dog *animal = [[Dog alloc] init];

return (id <AnimalVoiceProtocol>)animal;)

}  toProtocol:@protocol(AnimalVoiceProtocol)];

 

获取方式就是这样的:

id <AnimalVoiceProtocol> animal = [self getObject:@protocol(AnimalVoiceProtocol)];

 

调用: [animal  handle]

这样就把问题解决了;分析如下:

1、解除了Task对具体动物的强依赖(编译期依赖)

2、在运行时为Task提供正确的动物叫声,使其正常运转;

换句话说,就是将Task和Animal的依赖关系从编译期推迟到了运行时,所以我们需要把这种依赖关系在一个合适的时机注入到了运行时,这就是著名的依赖注入(DI)的由来;需要注意的时Task和Animal的依赖关系是解除不掉的,他们的依赖关系是依然存在,所以说,其实是解除的是强依赖,解除强依赖的手段就是将依赖关系从编译推迟到了运行时。

 

我们可以将运行中的项目当做主系统,这些接口以及背后的实现就是一个个插件,主系统并不依赖任何一个插件,当插件被系统加载的时候,主系统就可以调用适当的插件的功能。

 

延伸话题:

一般来说DI往往和Ioc(控制反转)联系到一起,IoC更多值IoC容器;

IoC即控制反转,该怎么理解IoC这个概念?

简单理解,从前,我们使用一个对象,除了销毁之外(iOSARC进行内存管理),这个对象的控制权在我们开发人员手里,这个控制权体现在对象的初始化、对属性赋值操作等,因为对象的控制权在我们手里,所以我们可以把这种情况称为“控制正转”。

那么控制反转就是将控制权交出去,交给IoC容器,让IoC容器去创建对象,给对象的属性赋值,这个对象的初始化过程是依赖于DI的,通过DI(依赖注入)实现IOC(控制反转)

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值