话说 依赖注入(DI) or 控制反转(IoC)

原文地址:http://www.thinkphp.cn/topic/12180.html

科普:
首先依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,鄙人学习了一下,看TP官网还没有相关的文章,就写下这篇拙作介绍一下这种设计模式,希望能为TP社区贡献一些力量。

首先先别追究这个设计模式的定义,否则你一定会被说的云里雾里,笔者就是深受其害,百度了N多文章,都是从理论角度来描述,充斥着大量的生涩词汇,要么就是java代码描述的,也生涩。

不管怎么样,总算弄清楚一些了,下面就以php的角度来描述一下依赖注入这个概念。

先假设我们这里有一个类,类里面需要用到数据库连接,按照最最原始的办法,我们可能是这样写这个类的:
  1. class example {
  2.     
  3.     private $_db;
  4.     function __construct(){
  5.         include "./Lib/Db.php";
  6.         $this->_db = new Db("localhost","root","123456","test");
  7.     }
  8.     function getList(){
  9.         $this->_db->query("......");//这里具体sql语句就省略不写了
  10.     }
  11. }
复制代码
过程:
在构造函数里先将数据库类文件include进来;
然后又通过new Db并传入数据库连接信息实例化db类;
之后getList方法就可以通过$this->_db来调用数据库类,实现数据库操作。

看上去我们实现了想要的功能,但是这是一个噩梦的开始,以后example1,example2,example3....越来越多的类需要用到db组件,如果都这么写的话,万一有一天数据库密码改了或者db类发生变化了,岂不是要回头修改所有类文件?
ok,为了解决这个问题,工厂模式出现了,我们创建了一个Factory方法,并通过Factory::getDb()方法来获得db组件的实例:
  1. class Factory {
  2.     public static function getDb(){
  3.         include "./Lib/Db.php";
  4.         return new Db("localhost","root","123456","test");
  5.     }
  6. }
复制代码
sample类变成:
  1. class example {
  2.     
  3.     private $_db;
  4.     function __construct(){
  5.         $this->_db = Factory::getDb();
  6.     }
  7.     function getList(){
  8.         $this->_db->query("......");//这里具体sql语句就省略不写了
  9.     }
  10. }
复制代码
这样就完美了吗?再次想想一下以后example1,example2,example3....所有的类,你都需要在构造函数里通过Factory::getDb();获的一个Db实例,实际上你由原来的直接与Db类的耦合变为了和Factory工厂类的耦合,工厂类只是帮你把数据库连接信息给包装起来了,虽然当数据库信息发生变化时只要修改Factory::getDb()方法就可以了,但是突然有一天工厂方法需要改名,或者getDb方法需要改名,你又怎么办?当然这种需求其实还是很操蛋的,但有时候确实存在这种情况,一种解决方式是:

我们不从example类内部实例化Db组件,我们依靠从外部的 注入,什么意思呢?看下面的例子:
  1. class example {
  2.     private $_db;
  3.     function getList(){
  4.         $this->_db->query("......");//这里具体sql语句就省略不写了
  5.     }
  6.     //从外部注入db连接
  7.     function setDb($connection){
  8.         $this->_db = $connection;
  9.     }
  10. }
  11. //调用
  12. $example = new example();
  13. $example->setDb(Factory::getDb());//注入db连接
  14. $example->getList();
复制代码
这样一来,example类完全与外部类解除耦合了,你可以看到Db类里面已经没有工厂方法或Db类的身影了。我们通过从外部调用example类的setDb方法,将连接实例直接注入进去。这样example完全不用关心db连接怎么生成的了。
这就叫 依赖注入,实现不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。

这还没完,我们再假设example类里面除了db还要用到其他外部类,我们通过:
  1. $example->setDb(Factory::getDb());//注入db连接
  2. $example->setFile(Factory::getFile());//注入文件处理类
  3. $example->setImage(Factory::getImage());//注入Image处理类
  4. ...
复制代码
我们没完没了的写这么多set?累不累?
ok,为了不用每次写这么多行代码,我们又去弄了一个工厂方法:
  1. class Factory {
  2.     public static function getExample(){
  3.         $example = new example();
  4.         $example->setDb(Factory::getDb());//注入db连接
  5.         $example->setFile(Factory::getFile());//注入文件处理类
  6.         $example->setImage(Factory::getImage());//注入Image处理类
  7.         return $expample;
  8.     }
  9. }
复制代码
实例化example时变为:
  1. $example=Factory::getExample();
  2. $example->getList();
复制代码
似乎完美了,但是怎么感觉又回到了上面第一次用工厂方法时的场景?这确实不是一个好的解决方案,所以又提出了一个概念: 容器,又叫做 IoC容器DI容器

我们本来是通过setXXX方法注入各种类,代码很长,方法很多,虽然可以通过一个工厂方法包装,但是还不是那么爽,好吧,我们不用setXXX方法了,这样也就不用工厂方法二次包装了,那么我们还怎么实现依赖注入呢?
这里我们引入一个约定:在example类的构造函数里传入一个名为Di $di的参数,如下:
  1. class example {
  2.     private $_di;
  3.     function __construct(Di &$di){
  4.         $this->_di = $di;
  5.     }
  6.     //通过di容器获取db实例
  7.     function getList(){
  8.         $this->_di->get('db')->query("......");//这里具体sql语句就省略不写了
  9.     }
  10. }
  11. $di = new Di();
  12. $di->set("db",function(){
  13.    return new Db("localhost","root","root","test"); 
  14. });
  15. $example = new example($di);
  16. $example->getList();
复制代码
Di就是IoC容器,所谓容器就是存放我们可能会用到的各种类的实例,我们通过$di->set()设置一个名为db的实例,因为是通过回调函数的方式传入的,所以set的时候并不会立即实例化db类,而是当$di->get('db')的时候才会实例化,同样,在设计di类的时候还可以融入单例模式。
这样我们只要在全局范围内申明一个Di类,将所有需要注入的类放到容器里,然后将容器作为构造函数的参数传入到example,即可在example类里面从容器中获取实例。当然也不一定是构造函数,你也可以用一个 setDi(Di $di)的方法来传入Di容器,总之约定是你制定的,你自己清楚就行。

这样一来依赖注入以及关键的容器概念已经介绍完毕,剩下的就是在实际中使用并理解它吧!

本文如有错误,请务必指出,大家共同学习,共同进步。

转载请注明出处。
阿里云
评论(22 相关
zhuyuseng 06月04日
不管别人怎么说,个人认为,说得通俗易懂就行。
zstxt1989 04月20日
这篇文章时间太过久远已经无法编辑,文章中那个时候的我对依赖注入的实现理解还是太嫩了,实际上类也不应当依赖$di对象,依赖注入的意思是通过反射分析类所依赖的其他类,从容器中获取相应的对象并自动注入到类里面。
回复 solomon_zjf 04月20日
重新来一发吧。
DSduoduo 03月20日
文章就不评论好坏了。发一下很不错的一篇 https://segmentfault.com/a/1190000002411255 
转发的文章 依赖注入与控制反转讲的思路非常经常,也完成本文的科普。而且对于关键的 实际项目中 用到 依赖注入与控制反转却需要另一个实用的东西,IoC容器。这个容器是第三方 协调注入与反转且更大程度的解耦! 这才是有用的东西。
ynke98328 02月09日
“依赖注入”,个人感觉是一种编程约定,并不是什么技术。或者说是一种编程架构。按照字面上的理解就是:用容器注入的方式减少程序间的依赖。倡导的是一种松耦合的编程习惯。便于代码的维护。感觉大家不必纠结用程序代码的实现。
wanzhende 09月29日
mark一下,学习
skylei 08月19日
天下文章一大抄!
fanyilong 07月17日
  1. IoC(Inversion of Controller) 控制反转(概念)
  2.     DI(Dependency Inject) 依赖注入(IoC概念中的一种类型实现)
  3.         通过依赖声明自动实例化依赖的类(通常通过反射实现)
  4.         Container 容器 
  5.         存储实例化对象 单例的一种实现工具
  6.         ServiceProvider 服务提供者
  7.         一次实例化一批(也可能是一个) 需要使用的类
  8.         并可做一个容器中对象的别名绑定
  9.         Factory 工厂
  10.         一个实例化类的对象 通过上层(框架)实例化
复制代码
声线 2016年05月23日
厉害,,能继续说说DI class吗
qdujunjie 2016年05月20日
楼主解释的很详尽,不知道耦合度到这一步还可不可以再降低?
yxz_blue 2016年05月11日
受益匪浅,很好的文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值