IoC — Inversion of Control,控制反转

DI — Dependency Injection,依赖注入

IOC,是一种设计原则:

通过将组件(Components)的设置和使用分开,来降低类别或模组之间的耦合度(即,解耦)

Martin Fowler,因认为“IoC”的意义易使人困惑,于2000年初,与多位IoC提倡者,给实际实践IOC的方式起一个更具体的名称— — “Dependency Injection (依赖注入)”。

控制反转IOC

控制反转,指实例依赖成员的『控制流程(Control Flow)』,由主动控制变成被动控制,因此被称为控制反转。

举个例子

小明有一台iPhone手机,他写了个程序来控制这个iphone手机来抢红包,自动发短信,自动抢购等等,这些过程可以用如下方式简单描述:

  • 打开iPhone,打开微信,进入群聊,抢红包
  • 打开iPhone,打开短信,编辑短信内容,选择收件人,发送短信
  • 打开iPhone,打开小米商城,选择需要抢购的商品,等待到达抢购时间,开始抢购
  • ······

大家很容易就能看出来问题,这些过程和iPhone耦合的很紧密,如果小明需要换手机了,那么那需要在每个过程中都将iPhone换为新手机。这时,就需要控制反转,我们不再主动去获得手机,而是被动的接收一个手机。

这样这些流程就可以更改为:

  • 接收手机
    • 打开手机,打开微信,进入群聊,抢红包
    • 打开手机,打开短信,编辑短信内容,选择收件人,发送短信
    • 打开手机,打开小米商城,选择需要抢购的商品,等待到达抢购时间,开始抢购

这样,我们发现,过程和具体的一个手机(例如,iPhone)耦合度降低了,可以很简单的更改手机的实例。

另一个例子

我们去网咖打游戏,是不可能自己带游戏去网咖的,而是网咖都把这些游戏提供好了。

需要的游戏,不用自己下载,而是网咖提供给你。

==>

需要的依赖实例,不用主动(Active)建立,而是被动(Passive)接收。

这就是控制反转(Inversion of Control)

简单来说,A依赖B,但A无法控制B的创建和销毁,仅使用B,那么B的控制权交给C(A以外)处理

  • 第一个例子,小明是A,依赖手机B,C可以是其他任何给小明手机的人(可能有点牵强)
  • 第二个例子,我们是A,依赖游戏B,C则是网吧

依赖注入DI

刚才那两个例子中,小明需要手机,我们需要玩游戏,那么有哪些方法可以让我们被动接收这些东西呢?假设小明是A,手机是B

  • 通过A的接口,把B传入
  • 通过A的构造,把B传入
  • 通过设置A的属性,把B传入

这个过程就是依赖注入,即A啥都没干,就可以直接使用B,比如我们啥也不用干,就可以直接玩游戏,真香。

也可以说,依赖注入是实现控制反转的一种方式,它们之间有着密切的联系

依赖倒置原则DIP

实际上,之前所说的控制反转是非常像:

依赖倒置原则(Dependency Inversion Principle, DIP)

这个原则是指,高阶模块不要依赖低阶模块,而是依赖抽象。

比如:

  • 我们喜欢吃油泼面
  • 我们也喜欢吃鱼香肉丝盖饭
  • 我们还喜欢吃江边城外烤全鱼
  • ······

那么,这些如果写到代码里,将会是非常紧密的耦合,假如有一天我们不喜欢吃油泼面,或者无法吃到油泼面,则代码非常多的地方需要更改

因此可以进一步抽象,

  • 我们喜欢吃面,我们→面←油泼面
  • 我们也喜欢吃盖饭,我们→盖饭←鱼香肉丝盖饭
  • 我们还喜欢吃烤鱼,我们→烤鱼←江边城外烤全鱼
  • ······

还可以更进一步的抽象

  • 我们喜欢吃食物,我们→食物←面,盖饭,烤鱼······

我们真正所需要的、依赖的,其实不是实际的类别与物件,而是它所拥有的功能。 其实这就是依赖倒置原则DIP (Dependency Inversion Principle)

进一步地:

  • 高阶模块不应该依赖于低阶模块,两者都该依赖抽象
  • 抽象不应该依赖于具体实现方式
  • 而是具体实作方式应该依赖抽象

实际上,面向接口编程就是一个具体地实践方法

IoC 容器(IoC Container)

IoC容器(又称:服务容器Service Container),随着依赖注入的频繁使用,来实现控制反转,会有很多重复代码,甚至随着技术的发展,有更多新的实现方法和方案,那么有人就把这些实现控制反转的代码打包成组件或框架,来避免人们重复造轮子。

广义上来说, IoC容器,就是有进行依赖注入的地方,你随便写一个类别,透过它将所需元件注入给高阶模组,便可说是容器。

但现在所说的『容器』,往往是泛指那些强大『框架』的容器

  • 动态生成实例,create,resolve,make等,为当前实例注入依赖
  • 注册依赖关系,利用配置或其他方式查找依赖关系,例如bind,register,link
  • ······

Reference