前言
之前已经记录过一篇文章解释依赖注入,现在看来有点过于理论化,于是写了一个demo解释下什么是依赖注入,也是在实际开发中依赖注入的使用场景。
对依赖注入还完全不理解的可以看一下上一篇文章:依赖注入 简介
场景
假设目前你正在开发公司的一个框架,为了保证线上代码的安全,你需要引入你们公司的“线上开关”的模块,这样如果发现你的框架有问题,可以及时用线上开关关闭部分功能,而不至于在下个版本发布前让问题一直存在。
一般情况下,开发就会直接依赖这个“线上开关”的模块,然后直接调用相关方法来判断是否要关闭部分功能。
这是最简单的实现方式,但是这样的方式会让你的库与“线上开关”的库耦合起来。
简单说几个真实存在问题:
- “线上开关”库更新了版本,更改了调用的API,原来的API废弃。那么你的库即使没有任何修改,你也必须更新你的库中依赖的“线上开关”的库。
- “线上开关”库更改了API中的数据结构,导致你的库中引用该API 的地方出了问题,而这对于“线上开关”这个库的开发人员是不可见的,他们不可能每个改动都要测下每个引用过他们的其他库。(你不能总是心里默认其他开发人员会去兼容以前的版本)
解决方式
解决步骤:
- 你的库中创建一个依赖注入类,一般是个单例。在库中引用这个单例的各个方法,这些方法由于可能会依赖其他库,因此不在库中直接实现,放到基础项目中去实现。
- 在基础项目中实现这个单例。
这样的好处有以下几点:
- 你不用依赖基础库之外的其他开发人员负责的库。因此你的项目也不会与其他库耦合,同时也一定程度上减小了你的库的大小。
- 把这个类的实现放到基础项目中,这样其他库的修改如果导致你的项目出了问题,其他库的开发人员也能注意到。
代码例子
impl包:基础项目代码。
testFrame包:使用依赖注入的库,即上面说的,你开发的那个库。
myFrame.java:你的框架的代码。
TestDependent.java:你的库中依赖注入的类。
TestMain.java:基础项目中的main方法。
TestDependentImpl.java:在基础项目中实现的依赖注入的类。
框架代码
myFrame
public class myFrame {
public void runPrint(){
if(TestDependent.getInstance().isCanRun()){
System.out.println("frame run");
}
}
}
TestDependent
public class TestDependent {
private static TestDependent instance;
public static void setInstance(TestDependent instance) {
TestDependent.instance = instance;
}
public static TestDependent getInstance() {
if(instance==null){
instance=new TestDependent();
}
return instance;
}
public boolean isCanRun(){
return true;
}
}
基础项目代码
TestMain
public class TestMain {
public static void main(String[] args){
//首先设置自己的依赖注入类
TestDependent.setInstance(new TestDependentImpl());
//调用框架
new myFrame().runPrint();
}
}
TestDependentImpl
public class TestDependentImpl extends TestDependent {
@Override
public boolean isCanRun() {
//执行线上开关逻辑,判断返回值
// if(){
// return true;
// }else{
// return false;
// }
return false;
}
}
总结
最终的流程是这样的:
- 你的库每次需要其他的依赖都会去TestDependent这个单例中拿。
- 由于基础项目中自己实现并覆盖了TestDependent这个单例,因此你的库实际是去TestDependentImpl中拿的。
- 这样不仅你的库与其他的库解耦了,其他的库的改动产生的问题也会第一时间反馈在基础项目上。
小tip
TestDependent为什么不用抽象类?
因为每个方法需要设置一个默认值,你要保证即使基础项目中不实现这个依赖注入类,你的库也能正常运行。