本文对应原书条目5,主要解释当一个类需要依赖底层资源时的一种有效引入底层资源的方式——依赖注入(Dependency Injection, DI)。如有问题或建议,欢迎联系我,谢谢~
什么是依赖注入?
让我们看看下面这个Car
类,它依赖了一个底层资源Engine
。传统的方式是在需要调用Engine
的地方new
一个出来。如下所示。
public class Car {
public void run() {
Engine engine = new Engine();
engine.ignite();
engine.shutDown();
}
}
传统地,如果一个类A依赖了另一个类B,在类A的相关方法里要自行new
出一个B的对象,然后去管理B对象的生命周期,这样就把两个类强耦合在一起了。类A不光要管好自己的业务逻辑,还要负责维护对象B的生命周期。
如果类A的多个方法里都要用到对象B,那你有可能会把对象B变成一个类A的成员变量,如下所示。
public class Car {
private final Engine engine = new Engine();
public void ignite() {
engine.ingite();
// 打开其他的部件
}
public void shutDown() {
engine.shutDown();
// 关闭其他的部件
}
}
这样一来,类Car
里的成员变量engine
可以在Car
对象里的所有方法中被使用了。但这样还是有问题的,有两点需求还是不能被满足。1)哪一天需要把Engine
替换成NewEngine
,还需要改代码;2)不同的Car
对象可能需要不同的Engine
。
因此我们就需要能够有方法把Car
依赖的Engine
这个资源注入进来。一般可以通过构造方法和setter
方法注入依赖的资源,如下所示。
public class Car {
private IEngine engine;
public Car(IEngine engine) {
this.engine = engine;
}
public void ignite() {
engine.ingite();
// 打开其他的部件
}
public void shutDown() {
engine.shutDown();
// 关闭其他的部件
}
public void setEngine(IEngine engine) {
this.engine = engine;
}
}
这里我们可以看到engine
从一个类变成了一个接口,这样做是为了支持为不同的Car
对象注入不同的IEngine
实现。实际调用的时候需要我们自己去注入engine
,如下所示。
...
IEngine engineA = new EngineA();
// 用构造器的方式注入
Car car = new Car(engineA);
car.iginte();
IEngine engineB = new EngineB();
// 用setter的方式注入
car.setEngine(engineB);
...
个人认为还是用构造器的方式注入资源会比较好,开放了setter
权限会让依赖的资源随意可变,从而变得混乱。
依赖注入框架
上面这种依赖注入是依靠客户端代码去实现的,还是不够便利,所幸我们有Spring
这种现代化的依赖注入框架,帮助我们去更好地实现依赖注入。像Spring这种容器主要是通过控制翻转(Inversion of Control, IoC)的方式实现依赖注入的。
那么怎么去理解这个控制反转呢?
原本需要我们程序员自己去创建一个对象A,然后在程序里再去创建它所依赖的对象B,再把对象B注入给对象A,这个控制权在程序上面。而控制反转是指你把这个创建依赖对象的职责交付给容器(即把对象的控制权交给容器)。你只要把自己写的类交托给IoC容器(比如Spring中的@Component
注解),然后再告诉容器什么地方需要注入这个资源(我们常用的@Autowired
注解),容器会通过反射的方式在一个对象创建的时候去自动地创建它所依赖的资源对象。程序员只需要去打造一个个组件,把拼接的工作交给IoC容器来完成。
依赖注入的适用场景
依赖注入适用于那些依赖了底层资源,且底层资源可能有多种实现的类。这种类一般不能实现为静态工具类或者单例。因为前者所依赖的资源必须以static
修饰且类本身一般不能支持实例化,而后者必须自己实现自己的实例化。所以这两种形式所依赖的资源和这些资源的初始化必须在自己的类里面写死,不是我们能控制的。也就是说“静态工具类和单例类不适合于需要引用底层资源的类”。[1]
总结
依赖注入能够降低程序内部的耦合度,提高对象自身的聚合度,让一个对象专注地处理自身的业务逻辑。这种松耦合能够提高类的“灵活性、可重用性和可测试性”。[1]
- 灵活性:一个类内部依赖的资源可被任意替换,而这个类本身无需更改;
- 可重用性:依赖注入反映了一种组件化的思维,我们编写的这些“组件”,可以在很多地方复用,避免了重复造轮子;
- 可测试性:因为对象与对象之间解耦了,我们可以针对单个对象进行周密的测试,而不用依赖其他对象。
声明
本文仅用于学习交流,请勿用于商业用途。转载请注明出处,如果涉及任何版权问题,请及时与我联系,谢谢!
参考资料
- 《Effective Java(第3版)》
- JAVA(一)依赖注入的简单理解 https://blog.csdn.net/muer_12...
- 控制反转和依赖注入的理解(通俗易懂) https://blog.csdn.net/sinat_2...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。