《依赖注入 初相见》

不诗意的女程序媛不是好厨师~
转载请注明出处,From李诗雨---https://blog.csdn.net/cjm2484836553/article/details/104449190

在这里插入图片描述

上篇我们学习了注解的基本知识,今天我们再来学习一下依赖注入。

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

那就让我们开始吧~

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();
    }
}

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

积累点滴,做好自己~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容