Shellbye.github.io icon indicating copy to clipboard operation
Shellbye.github.io copied to clipboard

什么是依赖注入(DI, Dependency Injection)?

Open Shellbye opened this issue 7 years ago • 0 comments

之前写Web一直是用Python+Django,短暂得写过一小段时间的Java,那时的Java Web真的是很麻烦,需要各种各样的配置,一会儿是注解,一会儿又是XML,写一个CRUD要先后在好几个不同的文件里写各种配置,有DAO,有TAO,还有XML,还有Entity,总之是非常复杂。相比之下,Django就方便的多,基本上是零配置,只需要专心写业务逻辑就好。最近又开始看了Java Web相关的Spring Boot 2.0,感觉好像好了很多,很多之前的麻烦东西都不存在了,越来越像Django了。

这次重拾Spring,又遇到了当年也没有搞明白的依赖注入(Dependency Injecting),借着这次机会,终于是搞懂了。

首先,依赖注入(Dependency Injecting)是一种保持代码解耦的程序设计方式。在正式理解依赖注入之前,需要先搞明白两个概念,一个是client(管控某些流程、业务逻辑),一个是service(提供某种服务、功能)。依赖注入,说的就是把client所依赖的service,注入到client中。下面我们举例说明:

public class AK47 {
    public void fire() {
        System.out.println("Fire!!!!")
    }
}

public class  Soldier {
    private AK47 ak47;

    Soldier() {
        this.ak47 = new AK47();
    }

    void fire() {
        this.ak47.fire()
    }

    public static void main(String[] args) {
        Soldier soldier = new Soldier();
        soldier.fire();
    }
}

上面的脚本中,我们的client就是Soldier,它依赖于AK47,然后它就在自己内部new一个AK47,然后在fire里面使用。这是没有依赖注入的写法,这样写的问题是当后期士兵的武器升级之后,武器的代码肯定得改,但是逻辑上讲士兵是不需要改的,但是按照我们上面的写法,士兵也得改,这就是两个类之间耦合度太高,需要解耦一下。

public interface Weapon {
    public void fire();
}
public class AK47 implements Weapon {
    public void fire() {
        System.out.println("Fire!!!!")
    }
}

public class Soldier {
    private Weapon weapon;

    Soldier(Weapon weapon1) {
        this.weapon = weapon1;
    }

    void fire() {
        this.weapon.fire()
    }

    public static void main(String[] args) {
        Weapon ak47 = new AK47();
        Soldier soldier = new Soldier(ak47);
        soldier.fire();

        Weapon m1 = new M1();
        Soldier soldier2 = new Soldier(m1);
        soldier2.fire();
    }
}

在新的方法中,我们把武器抽象出来了一个接口,然后在士兵初始化的时候传入武器,这样都有武器更新换代的时候,士兵的代码是不用动的,这样就实现了一定程度的士兵与武器的解耦。注意,这时,依赖(武器)就是通过Client(士兵)的构造方法被注入到Client中的,这就是一种依赖注入的方式。

上面这样通过构造方法注入的方式,有一定的缺点,那就是一旦Client创建好,就无法再更改了,于是还有一种通过setter方法进行注入,如下所示:

public interface Weapon {
    public void fire();
}
public class AK47 implements Weapon {
    public void fire() {
        System.out.println("Fire!!!!")
    }
}

public class  Soldier {
    private Weapon weapon;

    Soldier() {
    }

    void setWeapon(Weapon weapon1) {
        this.weapon = weapon1;
    }

    void fire() {
        this.weapon.fire()
    }

    public static void main(String[] args) {
        Weapon ak47 = new AK47();
        Soldier soldier = new Soldier();
        soldier.setWeapon(ak47);
        soldier.fire();
    }
}

上面的代码中,具体的实现一直在变,但是main方法一直其实没啥大的变化,在实际的工程项目中,Spring之类的框架,就是用来完成本文中main方法所做的事儿的,它们负责new各种个样的实例(术语Bean),然后按照一定的指引(XML或者是注解),讲service功能的Bean注入到client功能的Bean中。所以Spring这类的框架一般都叫做Injector,其与代码的关系,可以简单的用下图来展示:

ioc

Shellbye avatar Nov 19 '18 06:11 Shellbye