Answers
我试着搜了一下,中文资料基本都把IoC(控制翻转)和DI(依赖注入)混为一谈,就连 StackOverflow上的第一位答案 也犯了同样的问题(幸好下面有高票的反对评论,否则我的世界观都快坏掉了)
控制翻转是代码复用的一种模式(注意不是设计模式)
一般(非IoC)的复用,通常是用户代码调用组件(任意形式的被复用的代码,本答案中统称为组件)。也就是用户代码解决“Why it works, What to do”,组件解决“How to do”,逻辑的入口是用户代码,
而控制翻转则是组件来调用用户代码,也就是组件解决“Why it works, When to do”,用户代码解决“What to do”,逻辑的入口是组件
下面是除了DI之外的控制翻转的例子
- 接口/虚函数 组件调用接口/虚函数,具体由用户代码实现
- 发布订阅(事件)模式 组件触发事件,用户代码订阅事件
- 回调 用户代码写回调,组件来调用
- 几乎所有能被称之为框架的东西 用户代码在规定的地方实现具体业务逻辑,剩下的框架负责
通俗的说,一个项目由各个类组成。一个类在一个项目会被很多地方使用。
如果按照传统的写法就是每个用的地方都需要
new Class()
如果参数很复杂,每个地方都需要new Class(param1, param2, param3, ..)
此时每次修改类的构造函数, 那么每个地方需要跟着修改。工作量大,耦合度高。
但是可以把需要使用的类,初始化一次,放到一个容器中保存起来,其他需要使用的地方,
只需要调用容器的方法
Container->getClassINeed()
那么,使用这个类和生成这个类通过中间的容器分开了。
示例可以看
Phalcon
框架,文档里给的
例子
,教你一步步搭建自己的容器
一些管理对象生成的设计模式,本身也算一种容器的实现, 例如常见的工厂模式
IoC就是让框架为我们创建些东西。
App::bind('sms', function () {
// Create a new SMS component.
$sms = new SMSClient;
$sms->setUsername('shauna');
$sms->setPassword('1_5t341_c4t5!');
// Return the created SMS component.
return $sms;
});
据Clouser function特征,上面代的代码不会创建$sms对象。
$app->make('sms')的时候,$sms对象才被创建。所谓的反转大概就是按需创建吧。用的时候才触发创建,不是创建好了等着用。
因为程序员本身是农民工的一种,但是又想装的高大上一些,用以自赏,所以创建了这个很NB的术语,装的好像搞原子弹的。不用太在意。
本质是面向抽象编程,IOC描述的是特性,DI描述的具体实现方式,两者有共通的地方。可以这么理解:用DI实现具备IOC特性的框架,最终体现面向抽象编程的理念,增强系统的可扩性。
常见的“多态”就是面向抽象编程,比如:
Person person = new Male();
person.say();
person.run();
person.eat();
现在要把person指定为Female,则只需替换第一行即可:
Person person = new Female();
改动已经很少了,但仍可以做得更彻底一点:可不可以不更改现有代码就完成切换呢?答案就是"DI"。
撇开Spring不谈,咱们自己也完全可以实现一套最简化的类似功能:
Person person = SpringLike.getBeanFromConfig('person');
这行代码的意思就是,Person的实例是根据配置文件产生的,比如:
person=com.xx.Male
具体原理不赘述,大家耳熟能详。
回到上面那个问题,现在要把功能切换到Female,则只需更改配置文件即可:
person=com.xx.Female
将控制权进一步转移到配置文件,因而修改不涉及业务逻辑代码。
可以看出,“DI”是用更彻底的方式使用多态,在面向抽象编程这方面走得更远。"IOC"是面向抽象编程所体现出来的一个特性,本身和“DI”不冲突。
举个例子,有一天,你去庆丰包子铺吃包子,进门后你首先需要找到点餐台在哪,然后告诉服务员你要什么,然后去领餐口领包子。这是以你为主的流程,一切都在你的掌握之下,但如果你不主动,就没有包子吃。
第二天,你去另一家高大上包子铺吃包子,这里,你只要坐下来说一句我要吃包子,就有服务员给你把包子端上来。这是以餐厅为主的流程,你不需要知道去哪点餐,去哪领包子。
如果把第一个流程作为“正”的话,那第二个流程就是“反”过来了。这就是所谓的IoC。不过,这个名字起的不太容易理解,因为正、反的概念是相对的,你也可以说第二个流程是正的。
把这个概念应用到GUI编程上,就是顺序执行和事件处理的区别。本来是你自己写的程序按顺序执行,例如第一步输入用户名,第二部输入密码,然后执行登录操作。使用了Windows或者其他图形界面框架后,就是由框架来负责运行你的程序,并且在用户输入了内容之后通知你的程序,你再去做处理。相对于顺序执行,事件驱动就是IoC的一种表现形式。
把这个概念应用到依赖管理上,假设你的程序需要引用另一个对象,本来你是需要在代码里创建这个对象,应用IoC模式后,你的程序不再去主动创建第三方对象,而是运行在一个容器或者框架里,例如Spring,然后等着Spring把你需要的对象创建好传递进来。这就是依赖注入(DI),IoC的另一种表现形式。