PHP依赖注入(DI)和控制反转(IoC)详解


Posted in PHP onJune 12, 2017

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

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

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

先假设我们这里有一个类,类里面需要用到数据库连接,按照最最原始的办法,我们可能是这样写这个类的:

class example {
  
  private $_db;
  function __construct(){
    include "./Lib/Db.php";
    $this->_db = new Db("localhost","root","123456","test");
  }
  function getList(){
    $this->_db->query("......");//这里具体sql语句就省略不写了
  }
 }

过程:

在构造函数里先将数据库类文件include进来;
然后又通过new Db并传入数据库连接信息实例化db类;
之后getList方法就可以通过$this->_db来调用数据库类,实现数据库操作。

看上去我们实现了想要的功能,但是这是一个噩梦的开始,以后example1,example2,example3....越来越多的类需要用到db组件,如果都这么写的话,万一有一天数据库密码改了或者db类发生变化了,岂不是要回头修改所有类文件?
ok,为了解决这个问题,工厂模式出现了,我们创建了一个Factory方法,并通过Factory::getDb()方法来获得db组件的实例:

class Factory {
  public static function getDb(){
    include "./Lib/Db.php";
    return new Db("localhost","root","123456","test");
  }
 }

sample类变成:

class example {
  
  private $_db;
  function __construct(){
    $this->_db = Factory::getDb();
  }
  function getList(){
    $this->_db->query("......");//这里具体sql语句就省略不写了
  }
 }

这样就完美了吗?再次想想一下以后example1,example2,example3....所有的类,你都需要在构造函数里通过Factory::getDb();获的一个Db实例,实际上你由原来的直接与Db类的耦合变为了和Factory工厂类的耦合,工厂类只是帮你把数据库连接信息给包装起来了,虽然当数据库信息发生变化时只要修改Factory::getDb()方法就可以了,但是突然有一天工厂方法需要改名,或者getDb方法需要改名,你又怎么办?当然这种需求其实还是很操蛋的,但有时候确实存在这种情况,一种解决方式是:

我们不从example类内部实例化Db组件,我们依靠从外部的注入,什么意思呢?看下面的例子:

class example {
  private $_db;
  function getList(){
    $this->_db->query("......");//这里具体sql语句就省略不写了
  }
  //从外部注入db连接
  function setDb($connection){
    $this->_db = $connection;
  }
 }
 //调用
$example = new example();
$example->setDb(Factory::getDb());//注入db连接
$example->getList();

这样一来,example类完全与外部类解除耦合了,你可以看到Db类里面已经没有工厂方法或Db类的身影了。我们通过从外部调用example类的setDb方法,将连接实例直接注入进去。这样example完全不用关心db连接怎么生成的了。
这就叫依赖注入,实现不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。

这还没完,我们再假设example类里面除了db还要用到其他外部类,我们通过:

$example->setDb(Factory::getDb());//注入db连接
$example->setFile(Factory::getFile());//注入文件处理类
$example->setImage(Factory::getImage());//注入Image处理类
 ...

我们没完没了的写这么多set?累不累?
ok,为了不用每次写这么多行代码,我们又去弄了一个工厂方法:

class Factory {
  public static function getExample(){
    $example = new example();
    $example->setDb(Factory::getDb());//注入db连接
    $example->setFile(Factory::getFile());//注入文件处理类
    $example->setImage(Factory::getImage());//注入Image处理类
    return $expample;
  }
 }

实例化example时变为:

$example=Factory::getExample();
$example->getList();

似乎完美了,但是怎么感觉又回到了上面第一次用工厂方法时的场景?这确实不是一个好的解决方案,所以又提出了一个概念:容器,又叫做IoC容器、DI容器。

我们本来是通过setXXX方法注入各种类,代码很长,方法很多,虽然可以通过一个工厂方法包装,但是还不是那么爽,好吧,我们不用setXXX方法了,这样也就不用工厂方法二次包装了,那么我们还怎么实现依赖注入呢?
这里我们引入一个约定:在example类的构造函数里传入一个名为Di $di的参数,如下:

class example {
  private $_di;
  function __construct(Di &$di){
    $this->_di = $di;
  }
  //通过di容器获取db实例
  function getList(){
    $this->_di->get('db')->query("......");//这里具体sql语句就省略不写了
  }
 }
$di = new Di();
$di->set("db",function(){
  return new Db("localhost","root","root","test"); 
 });
$example = new example($di);
$example->getList();

Di就是IoC容器,所谓容器就是存放我们可能会用到的各种类的实例,我们通过$di->set()设置一个名为db的实例,因为是通过回调函数的方式传入的,所以set的时候并不会立即实例化db类,而是当$di->get('db')的时候才会实例化,同样,在设计di类的时候还可以融入单例模式。

这样我们只要在全局范围内申明一个Di类,将所有需要注入的类放到容器里,然后将容器作为构造函数的参数传入到example,即可在example类里面从容器中获取实例。当然也不一定是构造函数,你也可以用一个 setDi(Di $di)的方法来传入Di容器,总之约定是你制定的,你自己清楚就行。

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

PHP 相关文章推荐
PHP对接微信公众平台消息接口开发流程教程
Mar 25 PHP
php中隐形字符65279(utf-8的BOM头)问题
Aug 16 PHP
PHP中echo,print_r与var_dump区别分析
Sep 29 PHP
php启用sphinx全文搜索的实现方法
Dec 24 PHP
PHP获取当前日期和时间及格式化方法参数
May 11 PHP
php简单实现数组分页的方法
Apr 30 PHP
程序员的表白神器“520”大声喊出来
May 20 PHP
php遍历替换目录下文件指定内容的方法
Nov 10 PHP
PHP实现的简单AES加密解密算法实例
May 29 PHP
php转换上传word文件为PDF的方法【基于COM组件】
Jun 10 PHP
php中错误处理操作实例分析
Aug 23 PHP
php设计模式之正面模式实例分析【星际争霸游戏案例】
Mar 24 PHP
PHP正则删除HTML代码中宽高样式的方法
Jun 12 #PHP
浅谈PHP中的面向对象OOP中的魔术方法
Jun 12 #PHP
用php+ajax新建流程(请假、进货、出货等)
Jun 11 #PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
Jun 11 #PHP
php模仿qq空间或朋友圈发布动态、评论动态、回复评论、删除动态或评论的功能(中)
Jun 11 #PHP
PHP中error_reporting函数用法详细介绍
Jun 11 #PHP
详解PHP处理字符串类似indexof的方法函数
Jun 11 #PHP
You might like
Windows7下PHP开发环境安装配置图文方法
2010/05/20 PHP
php中通过数组进行高效随机抽取指定条记录的算法
2013/09/09 PHP
PHP使用mysqli操作MySQL数据库的简单方法
2017/02/04 PHP
Laravel学习教程之本地化模块
2017/08/18 PHP
php爬取天猫和淘宝商品数据
2018/02/23 PHP
php中Swoole的热更新实现代码实例
2021/03/04 PHP
DHTML Slide Show script图片轮换
2008/03/03 Javascript
JavaScript 对象的属性和方法4种不同的类型
2010/03/19 Javascript
JavaScript获取客户端计算机硬件及系统等信息的方法
2014/01/02 Javascript
javaScript中的this示例学习详解及工作原理
2014/01/13 Javascript
JS运动框架之分享侧边栏动画实例
2015/03/03 Javascript
微信小程序动态添加分享数据
2017/06/14 Javascript
javascript少儿编程关于返回值的函数内容
2018/05/27 Javascript
JavaScript实现无限级递归树的示例代码
2019/03/29 Javascript
JavaScript箭头函数中的this详解
2019/06/19 Javascript
js实现坦克大战游戏
2020/02/24 Javascript
[01:51]DAC趣味视频-如何成为职业选手.mp4
2017/04/02 DOTA
[01:37]全新的一集《真视界》——TI7总决赛
2017/09/21 DOTA
[01:04:35]2018DOTA2亚洲邀请赛 4.3 突围赛 Secret vs VG 第一场
2018/04/04 DOTA
Python图像处理之简单画板实现方法示例
2018/08/30 Python
python itchat实现调用微信接口的第三方模块方法
2019/06/11 Python
python自动化unittest yaml使用过程解析
2020/02/03 Python
Django自定义列表 models字段显示方式
2020/04/03 Python
玖熙女鞋美国官网:Nine West
2016/10/06 全球购物
法国家具及室内配件店:home24
2017/01/21 全球购物
J.Crew官网:美国知名休闲服装品牌
2017/05/19 全球购物
BookOutlet加拿大:在网上书店购买廉价折扣图书和小说
2018/10/05 全球购物
什么叫应用程序域?什么是托管代码?什么是强类型系统?什么是装箱和拆箱?什么是重载?CTS、CLS和CLR分别作何解释?
2012/05/23 面试题
2014庆六一活动方案
2014/03/02 职场文书
2014植树节活动总结
2014/03/11 职场文书
工会换届选举方案
2014/05/21 职场文书
中国世界遗产导游词
2015/02/13 职场文书
《富饶的西沙群岛》教学反思
2016/02/16 职场文书
python基础之爬虫入门
2021/05/10 Python
Python趣味挑战之实现简易版音乐播放器
2021/05/28 Python
德劲DE1105机评
2022/04/05 无线电