阅读 34

《依赖注入 初相见》

不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨---blog.csdn.net/cjm24848365…

在这里插入图片描述
上篇我们学习了注解的基本知识,今天我们再来学习一下依赖注入。

当让我们也不是一下子就来说依赖注入,而是秉着循序渐进的原则,一点一点的理解。

那就让我们开始吧~

1.什么是依赖(Dependency)?

通俗点来讲 依赖 就是一种需要。

依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义。

【举个栗子】:

例如一个人(Person)可以买车(Car)和房子(House),那Person类就依赖于Car类和House类。

public static void main(String[] args) {
    Person person = new Person();
    person.buy(new House());
    person.buy(new Car());
}

static class Person {
    //表示依赖House
    public void buy(House house) {
        System.out.println("买了套房");
    }

    //表示依赖Car
    public void buy(Car car) {
        System.out.println("买了辆车");
    }
}

static class House {
}

static class Car {
}
复制代码

嗯呐,了解了依赖是什么,接下来继续下一个概念:依赖倒置 ~

2.什么是 依赖倒置 ?

2.0 顺序依赖

有时候理解一个东西,通过反面来理解也不失为一种好的方法。

那依赖倒置的反面就是不依赖倒置了,就是顺序依赖了。

什么是顺序依赖呢?

比如说:

在这里插入图片描述

人的出行依赖于车子,那如果出行由自行车变成了汽车,人对应的类就要改变;如果又变成了火车,人对应的类又要改变。

人依赖于车子,车子一变,人就要跟着改变,这种就叫顺序的依赖。

public class Person {
    private Bike mBike;
    private Car mCar;
    private Train mTrain;

    public Person() {
        mBike = new Bike();
        //mCar = new Car();// ①变---改为汽车
        //mTrain = new Train();//②变---改为火车
    }

    public void goOut() {
        System.out.println("依赖于车子要出门了~");
        mBike.drive();
        //mCar.drive();  //①跟着变
        //mTrain.drive();  //②跟着变
    }

    public static void main(String... args) {
        Person person = new Person();
        person.goOut();
    }
}
复制代码

好的,了解了顺序的依赖,那依赖的倒置就是反过来喽,那就让我再来继续看看吧~

2.1依赖倒置的定义

依赖倒置是面向对象设计领域的一种软件设计原则。

依赖倒置原则(Dependence Inversion Principle,简称DIP)

  • 核心思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;
  • 说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。
  • 通俗来讲: 依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。
  • 问题描述: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
  • 解决方案: 将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。
  • 好处:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。

我们现在知道了依赖倒置原则的核心是:

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

那什么是上层模块和底层模块 ,什么又是抽象和细节呢?

我们来继续往下看~

2.2上层模块和底层模块

就拿一个公司来说吧,它一定有架构的设计、有职能的划分。按照职能的重要性,自然而然就有了上下之分。并且,随着模块的粒度划分不同这种上层与底层模块会进行变动,也许某一模块相对于另外一模块它是底层,但是相对于其他模块它又可能是上层。

在这里插入图片描述

如图,公司管理层就是上层,CEO 是整个事业群的上层,那么 CEO 职能之下就是底层。

然后,我们再以事业群为整个体系划分模块,各个部门经理以上部分是上层,那么之下的组织都可以称为底层。

由此,我们可以看到,在一个特定体系中,上层模块与底层模块可以按照决策能力高低为准绳进行划分。

那么,映射到我们软件实际开发中,一般我们也会将软件进行模块划分,比如业务层、逻辑层和数据层。

在这里插入图片描述

业务层中是软件真正要进行的操作,也就是做什么。 逻辑层是软件现阶段为了业务层的需求提供的实现细节,也就是怎么做。 数据层指业务层和逻辑层所需要的数据模型。

因此,如前面所总结,按照决策能力的高低进行模块划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层。

2.3抽象和具体

抽象如其名字一样,是一件很抽象的事物。

抽象往往是相对于具体而言的,具体也可以被称为细节。

比如:

1.交通工具是抽象,而公交车、单车、火车等就是具体了。

2.表演是抽象,而唱歌、跳舞、小品等就是具体。

由此可见,抽象可以是物也可以是行为。

在我们实际的软件开发中,抽象有两种形式:接口 & 抽象类

/**
 * Driveable 是接口,所以它是抽象
 */
public interface Driveable {
    void drive();
}
复制代码
/**
 * 而 Bike 实现了接口,它被称为具体。
 */
public class Bike implements Driveable {
    @Override
    public void drive() {
        System.out.println("Bike drive");
    }
}
复制代码

2.4依赖倒置的好处

在上述的人与车子的例子中,只要车子的类型一变,Person类就要跟着改动。

那有没有一种方法能让 Person 的变动少一点呢?

因为毕竟我们写的是最基础的演示代码,如果工程大了,代码复杂了,Person 面对需求变动时改动的地方会更多。

而依赖倒置原则正好适用于解决这类情况。

下面,我们尝试运用依赖倒置原则对代码进行改造。

我们再次回顾下它的定义。

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

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

首先是上层模块和底层模块的拆分。

按照决策能力高低或者重要性划分,Person 属于上层模块,Bike、Car 和 Train 属于底层模块。

上层模块不应该依赖于底层模块。

在这里插入图片描述

public class Person {
    //======顺序的依赖======
    //private Bike mBike;
    //private Car mCar;
    //private Train mTrain;

    //=====依赖倒置=====
    private Driveable mDriveable;

    public Person() {
        //======顺序的依赖======
        //mBike = new Bike();
        //mCar = new Car();// ①变---改为汽车
        //mTrain = new Train();//②变---改为火车

        //=====依赖倒置=====
        mDriveable = new Train(); //依赖倒置,只需要改这里就可以了,其他地方不用修改了

    }

    public void goOut() {
        System.out.println("依赖于车子要出门了~");
        //======顺序的依赖======
        //mBike.drive();
        //mCar.drive();  //①跟着变
        //mTrain.drive();  //②跟着变

        //=====依赖倒置=====
        mDriveable.drive();
    }

    public static void main(String... args) {
        Person person = new Person();
        person.goOut();
    }
}
复制代码

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

好的,通过上面的讲解相信你已经渐渐体会到什么是依赖倒置了。

但是,在编码的过程中,我们还是发现它的一个不足,那就是它不符合开闭原则。

每次我们都要修改Person类的内部,那我们能不能再不修改Person类内部的情况下,来实现同样的功能呢?

答案当时是肯定的,所以接下来我们再来学习一下 控制反转

3.什么是控制反转?

控制反转( IoC )是 Inversion of Control的缩写,意思就是对于控制权的反转。

上面的实例中,Person自己掌控着内部 mDriveable 的实例化。

现在,我们可以更改一种方式。将 mDriveable 的实例化移到 Person 外面。

public class Person2 {
    private Driveable mDriveable;

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

    public void goOut() {
        System.out.println("出门啦");
        mDriveable.drive();
    }

    public static void main(String... args) {
        Person2 person = new Person2(new Car());//变为了在Person的外部进行改变
        person.goOut();
    }
}
复制代码

就这样无论出行方式怎么变化,Person 这个类都不需要更改代码了。

在上面代码中,Person 把内部依赖的创建权力移交给了 Person2这个类中的 main() 方法。也就是说 Person 只关心依赖提供的功能,但并不关心依赖的创建。

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

好了,学习了这么多的概念,现在我们终于有了一定的基础来学习依赖注入了~

4.什么是依赖注入

依赖注入,也经常被简称为 DI,其实在上一节中,我们已经见到了它的身影。它是一种实现 IoC 的手段。什么意思呢?

为了不因为依赖实现的变动而去修改 Person,也就是说以可能在 Driveable 实现类的改变下不改动 Person 这个类的代码,尽可能减少两者之间的耦合。我们需要采用上一节介绍的 IoC 模式来进行改写代码。

这个需要我们移交出对于依赖实例化的控制权,那么依赖怎么办?Person 无法实例化依赖了,它就需要在外部(IoC 容器)赋值给它,这个赋值的动作有个专门的术语叫做注入(injection),需要注意的是在 IoC 概念中,这个注入依赖的地方被称为 IoC 容器,但在依赖注入概念中,一般被称为注射器 (injector)。

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

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

  1. 构造函数中注入
  2. setter 方式注入
  3. 接口注入
/**
 * 接口方式注入
 * 接口的存在,表明了一种依赖配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
复制代码
public class Person2 implements DepedencySetter {
    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

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

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

    public void goOut() {
        System.out.println("出门啦");
        mDriveable.drive();
    }

    public static void main(String... args) {
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}
复制代码

所以,依赖注入大家要记住的重点就是 : 依赖注入是实现控制反转的手段。

积累点滴,做好自己~