轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。

轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

版权声明:本文为博主原创文章,未经博主容许不得转载。 https://blog.csdn.net/briblue/article/details/75093382

写这篇文章的缘由是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来解释依赖注入这个概念。后来想一下,这些在面向对象开发过程当中与依赖相关的诸多术语和概念实际状况下很是的抽象,所以独立成文也有就必定的意义,旨在帮助初学者快速地对这些概念有一个大体的印象,而后经过一些实例来领悟它们之中的内在奥秘。html

什么是依赖(Dependency)?

依赖是一种关系,通俗来说就是一种须要。 
这里写图片描述java

程序员须要电脑,由于没有电脑程序员就没有办法编写代码,因此说程序员依赖电脑,电脑被程序员依赖。程序员

在面向对象编程中,代码能够这样编写。spring

class Coder {

    Computer mComputer;

    public Coder () { mComputer = new Computer(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Coder 的内部持有 Computer 的引用,这就是依赖在编程世界中的体现。数据库

依赖倒置 (Dependency inversion principle)

依赖倒置是面向对象设计领域的一种软件设计原则。 
软件设计有 6 大设计原则,合称 SOLID。编程

有人会有疑惑,设计原则有什么用呢?设计模式

设计原则是前辈们总结出来的经验,你能够把它们看做是内功心法。markdown

只要你在日常开发中按照设计原则进行编码,假以时日,你编程的功力将会大增。架构

依赖倒置原则的定义以下:框架

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

乍一看,这会让初学者摸不清头脑。这种学术性的归纳语言近乎于软件行业中的哲学。可实质上,它确实称得上是哲学,如今 SOLID 几乎等同于面向对象开发中的金科玉律,可是也正由于它的高度归纳、它的晦涩难懂,对于广大初学者而言这是一件很是不友好的事物。

咱们该怎么理解上面的定义呢?咱们须要咬文嚼字,各个突破。

什么是上层模块和底层模块?

无论你认可不认可,“有人的地方就有江湖”,咱们都说人人平等,可是对于任何一个组织机构而言,它必定有架构的设计有职能的划分。按照职能的重要性,天然而然就有了上下之分。而且,随着模块的粒度划分不一样这种上层与底层模块会进行变更,也许某一模块相对于另一模块它是底层,可是相对于其余模块它又多是上层。

这里写图片描述

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

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

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

那么,映射到咱们软件实际开发中,通常咱们也会将软件进行模块划分,好比业务层、逻辑层和数据层。 
这里写图片描述

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

所以,如前面所总结,按照决策能力的高低进行模块划分。业务层天然就处于上层模块,逻辑层和数据层天然就归类为底层。 
这里写图片描述

什么是抽象和细节?

抽象如其名字同样,是一件很抽象的事物。抽象每每是相对于具体而言的,具体也能够被称为细节,固然也被称为具象。

好比: 
1. 这是一幅画。画是抽象,而油画、素描、国画而言就是具体。 
2. 这是一件艺术品,艺术品是抽象,而画、照片、瓷器等等就是具体了。 
3. 交通工具是抽象,而公交车、单车、火车等就是具体了。 
4. 表演是抽象,而唱歌、跳舞、小品等就是具体。

上面能够知道,抽象能够是物也能够是行为。

具体映射到软件开发中,抽象能够是接口或者抽象类形式。

public interface Driveable { void drive(); } class Bike implements Driveable{ @Override public void drive() { // TODO Auto-generated method stub System.out.println("Bike drive."); } } class Car implements Driveable{ @Override public void drive() { // TODO Auto-generated method stub System.out.println("Car drive."); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Driveable 是接口,因此它是抽象,而 Bike 和 Car 实现了接口,它们被称为具体。

如今,咱们理解了依赖、上层模块、底层模块、抽象和具体。这样咱们能够正式开始学习依赖倒置原理这个概念了?

依赖倒置的好处

在日常的开发中,咱们大概都会这样编码。

public class Person { private Bike mBike; public Person() { mBike = new Bike(); } public void chumen() { System.out.println("出门了"); mBike.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

咱们建立了一个 Person 类,它拥有一台自行车,出门的时候就骑自行车。

public class Test1 { public static void main(String[] args) { // TODO Auto-generated method stub Person person = new Person(); person.chumen(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

执行结果以下:

出门了
Bike drive.
  • 1
  • 2
  • 3

不过,自行车适应很短的距离。若是,我要出门逛街呢?自行车就不大合适了。因而就要改为汽车。

public class Person { private Bike mBike; private Car mCar; public Person() { //mBike = new Bike(); mCar = new Car(); } public void chumen() { System.out.println("出门了"); //mBike.drive(); mCar.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

咱们须要修改 Person 这个类的代码。

不过,若是我要到北京去,那么汽车也不合适了。

class Train implements Driveable{
    @Override public void drive() { // TODO Auto-generated method stub System.out.println("Train drive."); } } package com.frank.test; 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 chumen() { System.out.println("出门了"); //mBike.drive(); //mCar.drive(); mTrain.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

咱们添加了 Train 这个最新的实现类,而后再次修改了 Person 这个类。

有没有一种方法能让 Person 的变更少一点呢?由于这是最基础的演示代码,若是工程大了,代码复杂了,Person 面对需求变更时改动的地方会更多。

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

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

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

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

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

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

上层模块不该该依赖于底层模块。 
可是

public class Person { private Bike mBike; private Car mCar; private Train mTrain; public Person() { //mBike = new Bike(); //mCar = new Car(); mTrain = new Train(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Person 这个类显然是依赖于 Bike 和 Car。Person 类中 chumen() 的能力彻底依赖于属性 Bike 或者 Car 对象,也就是说 Person 把本身的能力依赖在 Bike 和 Car 身上。

上层和底层都应该依赖于抽象。

这里写图片描述

咱们的代码中,Person 没有依赖抽象,因此咱们得引进抽象。

而底层的抽象是什么,是 Driveable 这个接口。

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 chumen() { System.out.println("出门了"); //mBike.drive(); //mCar.drive(); //mTrain.drive(); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

执行结果以下:

出门了
Train drive.
  • 1
  • 2

如今,Person 类中 chumen() 这个方法依赖于 Driveable 接口的抽象,它没有限定本身出行的可能性,任何 Car、Bike 或者是 Train 均可以的。

到这一步,咱们能够说是符合了上层不依赖于底层,依赖于抽象的准则了。

那么,抽象不该该依赖于细节,细节应该依赖于抽象又是什么意思呢?

以上面为例,Driveable 是抽象,它表明一种行为,而 Bike、Car、Train 都是实现细节。

Person 须要的是 Driveable,须要的是交通工具,但不是说交通工具必定是 Bike、Car、Train。将来也多是 AirPlane。

class AirPlane implements Driveable{
    @Override public void drive() { // TODO Auto-generated method stub System.out.println("AirPlane fly."); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

那么一个 Person,它下次出门改为飞机能够吗?固然能够的。由于依赖倒置的原因,Person 展示出了极度的可扩展性。

这里写图片描述

上面的内容就是依赖倒置原则。

有人会考虑到倒置这个词,我的的理解是倒置是改变的意思。

原本正常编码下,确定会出现上层依赖底层的状况,而依赖倒置原则的应用则改变了它们之间依赖的关系,它引进了抽象。上层依赖于抽象,底层的实现细节也依赖于抽象,因此依赖倒置咱们能够理解为依赖关系被改变,若是很是纠结于倒置这个词,那么倒置的实际上是底层细节,本来它是被上层依赖,如今它倒要依赖与抽象的接口。

这里写图片描述

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

控制反转 (IoC)

控制反转 IoC 是 Inversion of Control的缩写,意思就是对于控制权的反转,对么控制权是什么控制权呢? 
你们从新审视上面的代码。

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 chumen() { System.out.println("出门了"); //mBike.drive(); //mCar.drive(); //mTrain.drive(); mDriveable.drive(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

虽然,chumen() 这个方法再也不由于出行方式的改变而变更,可是每次更改出行方式的时候,Person 这个类仍是要修改。

Person 类仍是要实例化 mDriveable 的接口对象。

public Person() { //mBike = new Bike(); //mCar = new Car(); //mTrain = new Train(); mDriveable = new Train(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Person 本身掌控着内部 mDriveable 的实例化。 
如今,咱们能够更改一种方式。将 mDriveable 的实例化移到 Person 外面。

public class Person { private Driveable mDriveable; public Person(Driveable driveable) { this.mDriveable = driveable; } public void chumen() { System.out.println("出门了"); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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

public class Test1 { public static void main(String[] args) { // TODO Auto-generated method stub Bike bike = new Bike(); Car car = new Car(); Train train = new Train(); // Person person = new Person(bike); // Person person = new Person(car); Person person = new Person(train); person.chumen(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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

这种思想其实就是 IoC,IoC 是一种新的设计模式,它对上层模块与底层模块进行了更进一步的解耦。控制反转的意思是反转了上层模块对于底层模块的依赖控制。 
好比上面代码,Person 再也不亲自建立 Driveable 对象,它将依赖的实例化的权力交接给了 Test1。而 Test1 在 IoC 中又指代了 IoC 容器 这个概念。

再举一个例子,咱们到餐厅去叫外卖,餐厅有专门送外卖的外卖员,他们的使命就是及时送达外卖食品。

依照依赖倒置原则,咱们能够建立这样一个类。

public abstract class WaimaiYuan { protected Food food; public WaimaiYuan(Food food) { this.food = food; } abstract void songWaiMai(); } public class Xiaohuozi extends WaimaiYuan { public Xiaohuozi(Food food) { super(food); } @Override void songWaiMai() { System.out.println("我是小伙子,为您送的外卖是:"+food); } } public class XiaoGuniang extends WaimaiYuan { public XiaoGuniang(Food food) { super(food); } @Override void songWaiMai() { System.out.println("我是小姑娘,为您送的外卖是:"+food); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

WaimaiYuan 是抽象类,表明送外卖的,Xiaohuozi 和 XiaoGuniang 是它的继承者,说明他们均可以送外卖。WaimaiYuan 都依赖于 Food,可是它没有实例化 Food 的权力。

再编写食物类代码

public abstract class Food { protected String name; @Override public String toString() { return name; } } public class PijiuYa extends Food { public PijiuYa() { name = "啤酒鸭"; } } public class DuojiaoYutou extends Food { public DuojiaoYutou() { name = "剁椒鱼头"; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

Food 是抽象类,PijiuYa 和 DuojiaoYutou 都是实现细节。

IoC 少不了 IoC 容器,也就是实例化抽象的地方。咱们编写一个餐厅类。

public class Restaurant { public static void peican(int orderid,int flowid) { WaimaiYuan person; Food food; if ( orderid == 0) { food = new PijiuYa(); } else { food = new DuojiaoYutou(); } if ( flowid % 2 == 0 ) { person = new Xiaohuozi(food); } else { person = new XiaoGuniang(food); } person.songWaiMai(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

orderid 表明菜品编号,0 是啤酒鸭,其它则是剁椒鱼头。 
flowid 是订单的流水号码。 餐厅根据流水编码的不一样来指派小伙子或者小姑娘来送外卖,编写测试代码。

public class IocTest { public static void main(String[] args) { Restaurant.peican(0, 0); Restaurant.peican(0, 1); Restaurant.peican(1, 2); Restaurant.peican(0, 3); Restaurant.peican(1, 4); Restaurant.peican(1, 5); Restaurant.peican(1, 6); Restaurant.peican(0, 7); Restaurant.peican(0, 8); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

餐厅一次性送了 9 份外卖。

我是小伙子,为您送的外卖是:啤酒鸭
我是小姑娘,为您送的外卖是:啤酒鸭
我是小伙子,为您送的外卖是:剁椒鱼头
我是小姑娘,为您送的外卖是:啤酒鸭
我是小伙子,为您送的外卖是:剁椒鱼头
我是小姑娘,为您送的外卖是:剁椒鱼头
我是小伙子,为您送的外卖是:剁椒鱼头
我是小姑娘,为您送的外卖是:啤酒鸭
我是小伙子,为您送的外卖是:啤酒鸭
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

能够看到的是,由于有 Restaurant 这个 IoC 容器存在,大大地解放了外卖员的生产力,外卖员再也不依赖具体的食物,具体的食物也再也不依赖于特定的外卖员。也就是说,只要是食物外卖员就能够送,任何一种食物能够被任何一位外卖员送。

你们细细体会这是怎么样一种灵活性。若是非要外卖员本身决定配送什么食物,人少则还行,人多的时候,订单多的时候确定会乱成一锅粥。

因此,实际工做当中,基本上都是按照专业的人干专业的事这种基本规律运行。外卖员没有能力也没有义务去亲自决定该送什么订单,这种权力在于餐厅,只要餐厅配置好就 OK 了。

记住 配置 这个词。

在软件开发领域,相似餐厅这种调度配置而后决定依赖关系的 IOC 容器有许多框架好比 Spring。可是,因为我自己是 Android 开发的,对于 Spring 知之甚少,因此对这一块不作过多介绍。

但做为 IoC 容器,无非是针对配置而后动态生成依赖关系。有的配置是开发者按照规则编写在 xml 格式文件中,有些配置则是利用 Java 中的反射与注解。

IoC 模式最核心的地方就是在于依赖方与被依赖方之间,也就是上文中说的上层模块与底层模块之间引入了第三方,这个第三方统称为 IoC 容器,由于 IoC 容器的介入,致使上层模块对于它的依赖的实例化控制权发生变化,也就是所谓的控制反转的意思。

总之,由于 IoC 容器的存在,使得开发者编写大型系统工程的时候极大地解放了生产力。

依赖注入(Dependency injection)

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

public class Person { private Driveable mDriveable; public Person() { mDriveable = new Train(); } public void chumen() { System.out.println("出门了"); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

咱们再回顾 Person 这个类。在构造 Person 的时候,Person 内部初始化了 Driveable 对象,选择了 Train() 为实现,这种编码方式太具备局限性了。下次选择其它出行方式如 Bike 或者 Car 的时候,Person 这个类须要修改。

public class Person { private Driveable mDriveable; public Person() { mDriveable = new Bike(); //mDriveable = new Car(); //mDriveable = new Train(); } public void chumen() { System.out.println("出门了"); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

为了避免由于依赖实现的变更而去修改 Person,也就是说以可能在 Driveable 实现类的改变下不改动 Person 这个类的代码,尽量减小二者之间的耦合。咱们须要采用上一节介绍的 IoC 模式来进行改写代码。

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

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

再好比顾客去餐厅须要碗筷,可是顾客不须要本身带碗筷去,因此,在点菜的时候和服务员说,你给我一副碗筷吧。在这个场景中若是按照正常的编程方式,碗筷自己是顾客的依赖,可是应用 IoC 模式以后 ,碗筷是服务员提供(注入)给顾客的,顾客不用关心吃饭的时候用什么碗筷,由于吃不一样的菜品,可能餐具不一样,吃牛排用刀叉,喝汤用调羹,虽然顾客就餐时须要餐具,可是餐具的配置应该交给餐厅的工做人员。

这里写图片描述

若是以软件角度来描述,餐具是顾客是依赖,服务员给顾客配置餐具的过程就是依赖注入。

上一节的外卖员和菜品的例子,其实也是依赖注入的例子。

实现依赖注入有 3 种方式: 
1. 构造函数中注入 
2. setter 方式注入 
3. 接口注入

咱们如今一一观察这些方式

构造函数注入

public class Person { private Driveable mDriveable; public Person(Driveable driveable) { this.mDriveable = driveable; } public void chumen() { System.out.println("出门了"); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

优势:在 Person 一开始建立的时候就肯定好了依赖。 
缺点:后期没法更改依赖。

setter 方式注入

public class Person { private Driveable mDriveable; public Person() { } public void chumen() { System.out.println("出门了"); mDriveable.drive(); } public void setDriveable(Driveable mDriveable) { this.mDriveable = mDriveable; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

优势:Person 对象在运行过程当中能够灵活地更改依赖。 
缺点:Person 对象运行时,可能会存在依赖项为 null 的状况,因此须要检测依赖项的状态。

public void chumen() { if ( mDriveable != null ) { System.out.println("出门了"); mDriveable.drive(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

接口方式注入

public interface DepedencySetter { void set(Driveable driveable); } class Person implements DepedencySetter{ private Driveable mDriveable; public void chumen() { if ( mDriveable != null ) { System.out.println("出门了"); mDriveable.drive(); } } @Override public void set(Driveable driveable) { this.mDriveable = mDriveable; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这种方式和 Setter 方式很类似。有不少同窗可能有疑问那么加入一个接口是否是画蛇添足呢?

答案确定是否是的,这涉及到一个角色的问题。仍是之前面的餐厅为例,除了外卖员以外还有厨师和服务员,那么若是只有外卖员实现了一个送外卖的接口的话,那么餐厅配餐的时候就只会把外卖配置给外卖员。

接口的存在,代表了一种依赖配置的能力。

在软件框架中,读取 xml 配置文件,或者是利用反射技术读取注解,而后根据配置信息,框架动态将一些依赖配置给特定接口的类,咱们也能够说 Injector 也依赖于接口,而不是特定的实现类,这样进一步提升了准确性与灵活性。

总结

  1. 依赖倒置是面向对象开发领域中的软件设计原则,它倡导上层模块不依赖于底层模块,抽象不依赖细节。
  2. 依赖反转是遵照依赖倒置这个原则而提出来的一种设计模式,它引入了 IoC 容器的概念。
  3. 依赖注入是为了实现依赖反转的一种手段之一。
  4. 它们的本质是为了代码更加的“高内聚,低耦合”。

这里写图片描述

这篇文章我运用了大量的比喻来解释让给概念,我相信可以加深读者们对于这些概念的理解。

若是要获取更全面的信息,你们能够查看维基百科相关的页面。 
Dependency inversion principle 
Inversion_of_control 
Dependency injection

下篇文章,我会讲解在 Android 开发中很出名的依赖注入框架 Dagger2。

 
 
 

依赖注入和控制反转的理解,写的太好了。

 学习过Spring框架的人必定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来讲,总以为IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和你们分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解。

1、分享Iteye的开涛对Ioc的精彩讲解

  首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得很是通俗易懂,如下内容所有来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846

1.一、IoC是什么

  Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为什么是反转(有反转就应该有正转了),哪些方面反转了”,那咱们来深刻分析一下:

  ●谁控制谁,控制什么:传统Java SE程序设计,咱们直接在对象内部经过new进行建立对象,是程序主动去建立依赖对象;而IoC是有专门一个容器来建立这些对象,即由Ioc容器来控制对 象的建立;谁控制谁?固然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不仅是对象包括好比文件等)。

  ●为什么是反转,哪些方面反转了:有反转就有正转,传统应用程序是由咱们本身在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙建立及注入依赖对象;为什么是反转?由于由容器帮咱们查找及注入依赖对象,对象只是被动的接受依赖对象,因此是反转;哪些方面反转了?依赖对象的获取被反转了。

  用图例说明一下,传统程序设计如图2-1,都是主动去建立相关对象而后再组合起来:

图1-1 传统应用程序示意图

  当有了IoC/DI的容器后,在客户端类中再也不主动去建立这些对象了,如图2-2所示:

图1-2有IoC/DI容器后程序结构示意图

1.二、IoC能作什么

  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导咱们如何设计出松耦合、更优良的程序。传统应用程序都是由咱们在类内部主动建立依赖对象,从而致使类与类之间高耦合,难于测试;有了IoC容器后,把建立和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,因此对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得很是灵活。

  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序本来是老大,要获取什么资源都是主动出击,可是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来建立并注入它所须要的资源了。

  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找咱们,咱们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.三、IoC和DI

  DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并不是为软件系统带来更多功能,而是为了提高组件重用的频率,并为系统搭建一个灵活、可扩展的平台。经过依赖注入机制,咱们只须要经过简单的配置,而无需任何代码就可指定目标须要的资源,完成自身的业务逻辑,而不须要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为何须要依赖,谁注入谁,注入了什么”,那咱们来深刻分析一下:

  ●谁依赖于谁:固然是应用程序依赖于IoC容器;

  ●为何须要依赖:应用程序须要IoC容器来提供对象须要的外部资源;

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  ●注入了什么:就是注入某个对象所须要的外部资源(包括对象、资源、常量数据)。

  IoC和DI由什么关系呢?其实它们是同一个概念的不一样角度描述,因为控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),因此2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

  看过不少对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感受,读完以后依然是一头雾水,感受就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每个字,读完以后给人一种豁然开朗的感受。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。

2、分享Bromon的blog上对IoC与DI浅显易懂的讲解

2.一、IoC(控制反转)

  首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来讲,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,咱们是如何找女友的?常见的状况是,咱们处处去看哪里有长得漂亮身材又好的mm,而后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,而后嘿嘿……这个过程是复杂深奥的,咱们必须本身设计和面对每一个环节。传统的程序开发也是如此,在一个对象中,若是要使用另外的对象,就必须获得它(本身new一个,或者从JNDI中查询一个),使用完以后还要将对象销毁(好比Connection等),对象始终会和其余的接口或类藕合起来。

  那么IoC是如何作的呢?有点像经过婚介找女友,在我和女友之间引入了一个第三者:婚姻介绍所。婚介管理了不少男男女女的资料,我能够向婚介提出一个列表,告诉它我想找个什么样的女友,好比长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,而后婚介就会按照咱们的要求,提供一个mm,咱们只须要去和她谈恋爱、结婚就好了。简单明了,若是婚介给咱们的人选不符合要求,咱们就会抛出异常。整个过程再也不由我本身控制,而是有婚介这样一个相似容器的机构来控制。Spring所倡导的开发方式就是如此,全部的类都会在spring容器中登记,告诉spring你是个什么东西,你须要什么东西,而后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其余须要你的东西。全部的类的建立、销毁都由 spring来控制,也就是说控制对象生存周期的再也不是引用它的对象,而是spring。对于某个具体的对象而言,之前是它控制其余对象,如今是全部对象都被spring控制,因此这叫控制反转。

2.二、DI(依赖注入)

  IoC的一个重点是在系统运行中,动态的向某个对象提供它所须要的其余对象。这一点是经过DI(Dependency Injection,依赖注入)来实现的。好比对象A须要操做数据库,之前咱们老是要在A中本身编写代码来得到一个Connection对象,有了 spring咱们就只须要告诉spring,A中须要一个Connection,至于这个Connection怎么构造,什么时候构造,A不须要知道。在系统运行时,spring会在适当的时候制造一个Connection,而后像打针同样,注射到A当中,这样就完成了对各个对象之间关系的控制。A须要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3以后一个重要特征是反射(reflection),它容许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是经过反射来实现注入的。

  理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工做只是在spring的框架中堆积木而已。

3、我对IoC(控制反转)和DI(依赖注入)的理解

  在平时的java应用开发中,咱们要实现某一个功能或者说是完成某个业务逻辑时至少须要两个或以上的对象来协做完成,在没有使用Spring的时候,每一个对象在须要使用他的合做对象时,本身均要使用像new object() 这样的语法来将合做对象建立出来,这个合做对象是由本身主动建立出来的,建立合做对象的主动权在本身手上,本身须要哪一个合做对象,就主动去建立,建立合做对象的主动权和建立时机是由本身把控的,而这样就会使得对象间的耦合度高了,A对象须要使用合做对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,而且是紧密耦合在一块儿,而使用了Spring以后就不同了,建立合做对象B的工做是由Spring来作的,Spring建立好B对象,而后存储到一个容器里面,当A对象须要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,而后交给A对象使用,至于Spring是如何建立那个对象,以及何时建立好对象的,A对象不须要关心这些细节问题(你是何时生的,怎么生出来的我可不关心,能帮我干活就行),A获得Spring给咱们的对象以后,两我的一块儿协做完成要完成的工做便可。

  因此控制反转IoC(Inversion of Control)是说建立对象的控制权进行转移,之前建立对象的主动权和建立时机是由本身把控的,而如今这种权力转移到第三方,好比转移交给了IoC容器,它就是一个专门用来建立对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,经过IoC容器来创建它们之间的关系。

  这是我对Spring的IoC(控制反转)的理解。DI(依赖注入)其实就是IOC的另一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:得到依赖对象的方式反转了。

4、小结

  对于Spring Ioc这个核心概念,我相信每个学习Spring的人都会有本身的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。若是有理解不到位或者理解错的地方,欢迎广大园友指正!

相关文章
相关标签/搜索