Java内功修炼系列:依赖倒置、控制反转、依赖注入

目录

一 名词解释

1.1 依赖倒置原则(Dependency inversion principle)

1.2 上层/底层模块

1.3 依赖(Dependency)

二 依赖倒置

2.1 依赖倒置前

2.1 依赖倒置后

三 控制反转 (IoC)

四 依赖注入(Dependency injection)


一 名词解释

1.1 依赖倒置原则(Dependency inversion principle)

依赖倒置原则来源于软件设计 6 大设计原则,它的定义如下:

  • 上层模块不应该依赖底层模块,它们都应该依赖于抽象。

  • 抽象不应该依赖于细节,细节应该依赖于抽象。

说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。

通俗来讲: 依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。

问题描述: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案: 将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。

1.2 上层/底层模块

类比公司,管理层就是上层,CEO 是整个事业群的上层,那么 CEO 职能之下就是底层。各部门经理以上部分是上层,那么之下的组织都可以称为底层。由此,我们可以看到,在一个特定体系中,上层模块与底层模块可以按照决策能力高低为准绳进行划分。那么,映射到我们软件实际开发中,一般我们也会将软件进行模块划分,比如业务层、逻辑层和数据层。

业务层中是软件真正要进行的操作,也就是做什么。 逻辑层是软件现阶段为了业务层的需求提供的实现细节,也就是怎么做。 数据层指业务层和逻辑层所需要的数据模型。如前面所总结,按照决策能力的高低进行模块划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层。

1.3 依赖(Dependency)

简单一点说,A类中持有B类的引用,A类即依赖于B类。下面例子中,Person持有Car的引用,Person依赖于Car。

public class Person {

    private Car mCar;

    public Person() {
        mCar = new Car();
    }
}

二 依赖倒置

2.1 依赖倒置前

在之前的开发中,我们会这样写代码:

public class Person {

    private Bike mBike;
    private Car mCar;
    private Plane mPlane;

    public Person(){
        mBike = new Bike();
    }

    public void goToBeijing(){
        System.out.println("以某种交通方式去北京");
        mBike.drive();
    }

    public static void main(String ... args){
        Person person = new Person();
        person.goToBeijing();
    }
}

我们创建了一个 Person 类,Person可以选择任意一种交通方式去北京,当然不管哪种交通方式,开汽车也好坐飞机也好,每种交通方式都包含一个 goToBeijing()的方法供Person调用。

这是基础代码,类比实际代码中,肯定会经常性地更改交通工具,这时会频繁地更改Person中的依赖对象,也就是需要再Person类中new不同的交通工具(car or plane or bike),代码更改,多、烦、耦合高。

有没有方法能解决上述问题呢?依赖倒置!

2.1 依赖倒置后

在利用依赖倒置前,我们需要明白依赖倒置的定义:

  • 上层模块不应该依赖底层模块,它们都应该依赖于抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

首先是上层模块和底层模块的拆分,Person依赖交通出具出行,Person 属于上层模块,Bike、Car 和 Train 属于底层模块。之前的代码中,Person依赖于交通工具才能出行。

我们分析一下:Person只想去北京,去北京是抽象。具体怎么去北京,坐飞机还是开汽车,这是细节。上层模块也就是Person他应该依赖于抽象而不是细节,细节只要实现好去北京具体乘坐哪个交通方式(也就是方法的实现)就好了。

public class Person {

    private Bike mBike;
    private Car mCar;
    private Plane mplane;
    private Driveable mDriveable;

    public Person(){
       mDriveable = new Plane();
    }

    public void goToBeijing(){
        System.out.println("以某种交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person person = new Person();
        person.goToBeijing();
    }
}

Person无论选择哪种出行方式,去北京只要调用goToBeijing方法即可。该方法会自动调用出行交通工具的drive方法,因为bike也好坐小汽车也好,坐飞机也好,这三种出行方式都是实现了Driveable接口。该接口就是一个抽象,它不在乎你去北京的具体实现,但无论你选择哪种交通出行方式,都必须drive到北京。

可以看到,依赖倒置实质上是面向接口编程的体现。


三 控制反转 (IoC)

上述例子中,我们仍然需要对Person类内部进行代码修改,还是没有达到目的。因为Person仍然掌控着自己内部的mDriveable 具体实现类的实例化。 我们需要将 mDriveable 的实例化移到 Person 外面,也就是说,Person将自己掌控着的内部 mDriveable 的实例化的这个控制权反转交出去了,这就是控制反转,也就是 IoC (Inversion of Control) 。

public class Person {

    private Driveable mDriveable;

    public Person(Driveable driveable){
        this.mDriveable = driveable;
    }

    public void goToBeijing(){
        System.out.println("以某种交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person2 person = new Person2(new Car());
        person.goToBeijing();
    }

}

就这样无论Person选择什么交通方式去北京,都不需要再去更改Person 类的内部了。Person 把内部依赖的创建权力移交给了类中的 main() 方法,它只关心依赖提供的功能,而不关心依赖的创建,不再亲自创建 Driveable 对象。而Person这个类在 IoC 中,指代了 IoC 容器这个概念。

这种思想其实就是 IoC,IoC 是一种新的设计模式,它对上层模块与底层模块进行了更进一步的解耦。控制反转的意思是反转了上层模块对于底层模块的依赖控制。


四 依赖注入(Dependency injection)

上面是控制反转,那么如何实现控制反转呢?

答:依赖注入 ,DI , Dependency Injection。也经常被简称为 DI,它是一种实现 IoC 的手段。

上面的例子中我们移交出了Person对于依赖实例化的控制权,那么依赖怎么办?Person 无法实例化依赖了,它就需要在外部(IoC 容器)赋值给它,这个赋值的动作有个专门的术语叫做注入(injection)。

需要注意的是在 IoC 概念中,这个注入依赖的地方被称为 IoC 容器,但在依赖注入概念中,一般被称为注射器 (injector)。

表达通俗一点就是:我不想自己实例化依赖,你(injector)创建它们,然后在合适的时候注入给我

实现依赖注入有 3 种方式:

  1. 构造函数中注入

  2. setter 方式注入

  3. 接口注入

/**
 * 接口方式注入
 * 接口的存在,表明了一种依赖配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person  implements DepedencySetter {

    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

    //构造函数注入
    public Person(Driveable driveable){
        this.mDriveable = driveable;
    }

    //setter 方式注入
    public void setDriveable(Driveable mDriveable) {
        this.mDriveable = mDriveable;
    }

    public void goToBeijing(){
        System.out.println("以某种交通方式去北京");
        mDriveable.drive();
    }

    public static void main(String ... args){
        Person2 person = new Person2(new Car());
        person.goToBeijing();
    }
}

参考文章:

轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI):https://blog.csdn.net/briblue/article/details/75093382

发布了174 篇原创文章 · 获赞 433 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/LucasXu01/article/details/105462568