首先,本文探讨的目的是如何让系统的扩展性变高,以实现降低迭代成本。追求开发速度的完全不必理会。
IoC 控制反转
IoC(Inverse of Control),控制反转,但这个说法不够直白,在实际生产中,用控制转移来描述反而地道一点。今天不抠字眼,对象的控制权转交给第三方,往往就是:某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。 但还是很难理解。
DI 依赖注入
随后Martin Fowler提出了DI(Dependency Injection)依赖注入的概念用以代替IoC,即:
让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
“依赖注入”显然比“控制反转”更好理解。
看一个例子
interface I {
void foo();
class A implements I{
public A() {
//...
}
//...
}
class B {
I i;
public B() {
i = new A();
//...
}
public void bar() {
}
}
可以看到,类B的功能依赖于A,而且这里使用了组合。
当然,使用组合要比让B继承A并进行扩展好得多,这不是今天要讨论的。
这个例子的问题在于:B依赖的A由B构建。看起来好像没什么问题,但是:
- B负责了A的创建,不符合SRP 单一职责原则。
- 当A业务扩展时,可能构造函数 发生改变,这样就要修改B的构造函数,不符合OCP 开闭原则
- 如果不面向接口编程的话,这个代码会更加糟糕,为了后面方便,这里已经提出了接口I(以减少本文的注意力分散)
- 倘若业务发展,B需要有子类时,很大可能是重构,(因为很大可能不符合里氏代换原则,就不往下讨论了)。
这就是大家说的耦合变高了。
按照IoC或者DI来构建代码,可以减少这样的问题。
依赖注入的方式
- 构造函数注入
- setter方法注入
- 接口注入
- 注解注入
构造函数注入
class B {
I i;
public B(I i) {
this.i = i;
}
}
这样选择控制权已经不再属于B(IoC想描述的),一种注入形式实现了依赖对象的获得(DI想描述的)。
setter方法注入
class B {
I i;
public B() {
}
public void setI(I i) {
this.i = i;
}
}
接口注入
定义注入接口:
上面接口名字定的不好,暂不修改了
interface InjectDependencyI {
void setI(I i);
}
class B implements InjectDependencyI {
I i;
public B() {
}
public void setI(I i) {
this.i = i;
}
}
和setter注入差别不大,但是当接口I的客户有多个而且没有继承关系或者兄弟关系时,使用接口注入效果会更好一点,代码整体的可读性会好一点。
注解注入
显然,这里存在一个IoC容器,按照规则帮我们进行一定的自动化处理,减少了手动注入的工作,而这些规则是使用注解描述的。
Android中使用较多的是Dagger/Dagger2.本篇就先不扩展了。
写在最后
为什么在Android客户端工作中我们比较难体会到IoC/DI的作用呢?
往往实际情况是:更多的注意力在界面、交互上,业务部分没有那么庞大,某一种业务的客户可能只有一个,即使当前的设计耦合高了不利于扩展,但根本没有矛盾爆发的机会。