什么是依赖注入(DI)和控制反转(IOC)
介绍
依赖注入,Dependency Injection。
在说明依赖注入之前,我们先描述一个场景。
玩家类依赖于武器进行攻击。
在传统的编程逻辑中,代码如下。可以看到,Player玩家不仅依赖于Weapon,而且还依赖于Sword。而且可以预见的是,假如Sword剑换成枪Spear,需要重新创建一个玩家类,这会导致大量的重复代码,不仅编写时麻烦,修改时还容易忘记改。
class Player {
Weapon weapon;
Player() {
this.weapon = new Sword(); // 直接创建Sword对象,耦合度较高
}
public void attack() {
weapon.attack();
}
}
依赖注入
class Player {
Weapon weapon;
Player(Weapon weapon) {
this.weapon = weapon; // 通过构造函数注入Weapon对象
}
public void attack() {
weapon.attack();
}
}
以上的方法就是依赖注入,其核心思想就是:将类的实例化延迟到用户使用时。
可以看到,Player现在仅依赖一个Weapon类,而使用不同的武器只要继承Weapon并传入Player就可以了。
现在,我们再回到“依赖注入”这个名词方面,“依赖”,指的是作为参数被传入方法中的对象,比如在上面的例子中,我们可以把Sword的对象传给Player构造函数。而“注入”则代表这个Sword对象(叫做依赖)被注入给了Player对象。所以依赖注入是把一个对象注入给另一个对象。
Player player = new Player(new Sword());
// 假如换成枪/矛
Player player = new Player(new Spear());
不难发现,“依赖注入”所做的事情,就是把依赖的创建延迟到外部。
控制反转(IoC)
Inversion of Control,直译“控制反转”。
依赖注入说了半天,原来只是用了一下多态,那为啥我还是看不懂Spring呢?这就不得不说IoC了。
控制反转的意思就是
将对象或程序部分的控制权转移到容器或框架中
。而一般来说,都是对象创建的过程移交给了容器,比如Spring容器。到这里两个词其实都解释明白了。
以Spring的Autowired为例
在Spring中,获取对象自不必说,使用@Component等注解就可以把该类交给Spring容器控制(控制反转)。而Autowired自动装配就是用来注入依赖的。
public interface Weapon {
void attack();
}
@Component
public class Sword implements Weapon {
@Override
public void attack() {
System.out.println("Swinging a sword!");
}
}
@Component
public class Player {
// 使用@Autowired注解自动注入Weapon类型的依赖
@Autowired
private Weapon weapon;
public void fight() {
weapon.attack();
}
}
以上代码实现的功能是一样的,一位拿着剑的玩家
。
那么其实很让人疑惑,Autowired或者说控制反转的优势在哪里呢?
假如我们需要一位拿着枪/矛的玩家,我们可以这样修改:
@Component("mySpear")
public class Spear implements Weapon {
// ...
}
@Autowired
@Qualifier("mySpear")
private Weapon weapon;
可能有人已经发现了,Spring的控制反转,更注重一个对象的创建,而不是类本身。而Java不能单独创建对象,只能创建类。通过以上的例子,我们既可以创建一个拿着剑的玩家,也可以创建一个拿着枪的玩家,而Spring Bean的大多数情况下,一个类只有一个实例(所以Autowired非常好用)。我们要创建一个拿着不同武器的玩家,不需要写一个新的Player类,也不需要new Player(new Spear());
,而只需要修改注解即可,因为我们实际上只关心一个Player对象而不是两个或者更多。
这个例子举得不是很好,但是我想说的就是Spring的依赖注入让我们更像是在编写一个对象,而不是一个类,他让我们把对象的信息写到了类中,并自动实例化放到容器里,我们可以通过Autowired等方式获取这个对象并直接使用,而不是到使用时才去实例化这个对象。这样的好处在于,使用某个对象时,代码将非常精简;当我们想要让玩家换武器时,只需要新写一个武器,修改一下注入即可,测试起来很方便,而且由于是依赖接口的(而非具体实例),因此相对稳定,如果修改后出现问题,多半是新的武器写得有问题。