PHP进阶学习之依赖注入与Ioc容器详解


Posted in PHP onJune 19, 2019

本文实例讲述了PHP依赖注入与Ioc容器。分享给大家供大家参考,具体如下:

背景

在很多编程语言(例如java)开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,一旦有修改,牵扯的类会很多。

最早在java的spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。目前许多主流PHP框架也使用了依赖注入容器,如ThinkPHP、Laravel等。

一、概念

1、容器:字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,现在我们讨论的是这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调(闭包),通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦”、“依赖注入”。

2、IoC - Inversion of Control 控制反转 

控制反转是从容器的角度在描述,即:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。 

3、DI - Dependency Injection 依赖注入 

依赖注入是从应用程序的角度在描述,可以把依赖注入,即:应用程序依赖容器创建并注入它所需要的外部资源。

备注:依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,从某个方面讲,就是它们描述的角度不同。 

二、依赖注入的原理

一般情况下,当存在类与类之间的依赖关系的时候,我们都是通过直接实例化的方式进行调用。一旦出现多层依赖,这种方式的耦合程度就很高,在需要修改其中一个类的时候,会牵扯很多依赖它的类的修改,因此对代码的改动会比较大。

下面简单举一个A->B->C三层依赖的关系解释怎么运用依赖注入来解耦,提高开发效率。

PHP进阶学习之依赖注入与Ioc容器详解

而依赖注入方式如下:

PHP进阶学习之依赖注入与Ioc容器详解

解析:

常规写法里面,一旦C类需要作出改变,或者B类的调用需要改变成D类的时候,还需要考虑到依赖自己的B类,即还需要对B类作出修改。

依赖注入的思想就是即用即实例,反转类与类之间的控制关系,实现由调用类A类控制后续的依赖关系,这样可以让B类随意的更改所需依赖和实例化的类(C类或D类),达到解耦的目的。

PHP进阶学习之依赖注入与Ioc容器详解

三、常用的依赖注入方式:

1、构造方法注入;2、set属性注入;3、静态工厂方法注入;

上述的例子使用的就是构造方法注入的方式,将对象作为参数传递到构造方法中;同样的set属性注入也是相类似的方法,不同的仅仅是在set一个类的成员的属性时传递这个对象参数,在此就不一一举例了。

除此之外,还有静态工厂方法注入的方式,这种方法与静态工厂方法类似。

我们知道静态工厂方法就是通过一个类来管理需要实例化的多个相似的类,该类会定义一个方法用于获取需要实例化的对象,而具体要实例化哪个对象就依赖于传递进来的对象名参数了。

对于静态工厂方式的注入,与一般的静态工厂方法不同之处在于这个传进来的参数是一个已经实例化过的对象。

<?php
class IoC
{
  protected static $registry = [];
  public static function bind($name, Callable $resolver) //传入类名和类对象实例
  {
    static::$registry[$name] = $resolver;
  }
  public static function make($name) //静态工厂方法
  {
    if (isset(static::$registry[$name])) {
      $resolver = static::$registry[$name];
      return $resolver(); //实例化
    }
    throw new Exception('Alias does not exist in the IoC registry.');
  }
}

总而言之,三种方式传递的都是实例化对象,只是不同之处在于传递的位置分别为构造方法、set属性、静态工厂方法而已。

四、依赖注入容器(Ioc容器)

大多数时侯,在使用依赖注入方式解耦组件时,并不需要用到容器。
当一段程序需要实例化的类太多或者依赖太多的时候,重复依赖注入的代码是比较繁琐的事情,例如以下情况:

PHP进阶学习之依赖注入与Ioc容器详解

当产生以上关系的时候,依赖注入的代码会比较混乱,而且存在重复,更有可能在调用一个一般方法时new一个不需要的类,产生冗余。

此时需要使用容器,使用依赖注入容器后的思路是应用程序需要到A类,就从容器内取得A类。具体是容器创建C类,再创建B类并把C注入,再创建A类,并把B类注入,应用程序调用A类方法, A类调用B类方法,接着做些其它工作.总之容器负责实例化,注入依赖,处理依赖关系等工作。

PHP进阶学习之依赖注入与Ioc容器详解

PHP进阶学习之依赖注入与Ioc容器详解

对于实际开发中复杂多变的代码环境,我们并不能完全知道现在的类在未来会扩展成什么情况,因此我们需要在有新的依赖类加入的时候,通过容器去实现实例化该类的方法。因此,在实例化未知类的时候,最能探索一个类的内部结构和实例化的方法就是利用反射,由此可知,反射是容器管理各个依赖类的核心。我们可以通过实例来了解容器的内部实现:

三个存在依赖关系的类:文件testClass.php

<?php //依赖关系:Company->Department->Group
class Group
{
  public function doSomething()
  {
    echo __CLASS__.":".'hello', '|';
  }
}
class Department
{
  private $group;
  public function __construct(Group $group)
  {
    $this->group = $group;
  }
  public function doSomething()
  {
    $this->group->doSomething();
    echo __CLASS__.":".'hello', '|';
  }
}
class Company
{
  private $department;
  public function __construct(Department $department)
  {
    $this->department = $department;
  }
  public function doSomething()
  {
    $this->department->doSomething();
    echo __CLASS__.":".'hello', '|';
  }
}

Ioc容器的内部实现:

<?php
class Container
{
  private $s = array();
  public function __set($k, $c)
  {
    $this->s[$k] = $c;
  }
  public function __get($k)
  {
    return $this->build($this->s[$k]);
  }
  /**
   * 自动绑定(Autowiring)自动解析(Automatic Resolution)
   *
   * @param string $className
   * @return object
   * @throws Exception
   */
  public function build($className)
  {
    // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures)
    if ($className instanceof Closure) {
      // 执行闭包函数,并将结果
      return $className($this);
    }
    /*通过反射获取类的内部结构,实例化类*/
    $reflector = new ReflectionClass($className);
    // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
    if (!$reflector->isInstantiable()) {
      throw new Exception("Can't instantiate this.");
    }
    /** @var ReflectionMethod $constructor 获取类的构造函数 */
    $constructor = $reflector->getConstructor();
    // 若无构造函数,直接实例化并返回
    if (is_null($constructor)) {
      return new $className;
    }
    // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
    $parameters = $constructor->getParameters();
    // 递归解析构造函数的参数
    $dependencies = $this->getDependencies($parameters);
    // 创建一个类的新实例,给出的参数将传递到类的构造函数。
    return $reflector->newInstanceArgs($dependencies);
  }
  /**
   * @param array $parameters
   * @return array
   * @throws Exception
   */
  public function getDependencies($parameters)
  {
    $dependencies = [];
    /** @var ReflectionParameter $parameter */
    foreach ($parameters as $parameter) {
      /** @var ReflectionClass $dependency */
      $dependency = $parameter->getClass();
      if (is_null($dependency)) {
        // 是变量,有默认值则设置默认值
        $dependencies[] = $this->resolveNonClass($parameter);
      } else {
        // 是一个类,递归解析
        $dependencies[] = $this->build($dependency->name);
      }
    }
    return $dependencies;
  }
  /**
   * @param ReflectionParameter $parameter
   * @return mixed
   * @throws Exception
   */
  public function resolveNonClass($parameter)
  {
    // 有默认值则返回默认值
    if ($parameter->isDefaultValueAvailable()) {
      return $parameter->getDefaultValue();
    }
    throw new Exception('I have no idea what to do here.');
  }
}
require_once "./testclass.php"; //开始测试,先测试已知依赖关系的情况
$c = new Container();
$c->department = 'Department';
$c->company = function ($c) {
  return new Company($c->department);
};
// 从容器中取得company
$company = $c->company;
$company->doSomething(); //输出: Group:hello|Department:hello|Company:hello|
// 测试未知依赖关系,直接使用的方法
$di = new Container();
$di->company = 'Company';
$company = $di->company;
$company->doSomething();//输出: Group:hello|Department:hello|Company:hello|

我们可以通过一张图解释Ioc容器的内部逻辑:

PHP进阶学习之依赖注入与Ioc容器详解

五、总结

IOC的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。Spring容器负责将这些联系在一起。也就是说,Spring的IOC负责管理各种对象的创建、清除以及它们之间的联系。 

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
php实现网站插件机制的方法
Nov 10 PHP
php下正则来匹配dede模板标签的代码
Aug 21 PHP
通过JavaScript或PHP检测Android设备的代码
Mar 09 PHP
PHP删除HTMl标签的实现代码
Jun 30 PHP
php加密解密实用类分享
Jan 07 PHP
利用php下载xls文件(自己动手写的)
Apr 18 PHP
php数组键名技巧小结
Feb 17 PHP
php简单计算页面加载时间的方法
Jun 19 PHP
PHP基于工厂模式实现的计算器实例
Jul 16 PHP
php开发工具有哪五款
Nov 09 PHP
PHP7新增运算符用法实例分析
Sep 26 PHP
PHP登录(ajax提交数据和后台校验)实例分享
Dec 29 PHP
yii2 在控制器中验证请求参数的使用方法
Jun 19 #PHP
php自定义排序uasort函数示例【二维数组按指定键值排序】
Jun 19 #PHP
windows 2008r2+php5.6.28环境搭建详细过程
Jun 18 #PHP
PHP进阶学习之类的自动加载机制原理分析
Jun 18 #PHP
PHP进阶学习之垃圾回收机制详解
Jun 18 #PHP
PHP进阶学习之命名空间基本用法分析
Jun 18 #PHP
PHP进阶学习之反射基本概念与用法分析
Jun 18 #PHP
You might like
一个php作的文本留言本的例子(一)
2006/10/09 PHP
Nginx下配置codeigniter框架方法
2015/04/07 PHP
PHP Trait功能与用法实例分析
2020/06/03 PHP
导航跟随滚动条置顶移动示例代码
2013/09/11 Javascript
用于deeplink的js方法(判断手机是否安装app)
2014/04/02 Javascript
基于jquery实现在线选座订座之影院篇
2015/08/24 Javascript
Javascript生成全局唯一标识符(GUID,UUID)的方法
2016/02/27 Javascript
前端js弹出框组件使用方法
2020/08/24 Javascript
使用jQuery监听扫码枪输入并禁止手动输入的实现方法(推荐)
2017/03/21 jQuery
JavaScript数据结构之二叉查找树的定义与表示方法
2017/04/12 Javascript
vue2.0使用swiper组件实现轮播效果
2017/11/27 Javascript
js读取本地文件的实例
2017/12/22 Javascript
JavaScript使用享元模式实现文件上传优化操作示例
2018/08/07 Javascript
详解一个基于react+webpack的多页面应用配置
2019/01/21 Javascript
[02:06]DOTA2肉山黑名单魔法终结者 敌法师中文配音鉴赏
2013/06/17 DOTA
[02:43]DOTA2英雄基础教程 半人马战行者
2014/01/13 DOTA
Python中使用glob和rmtree删除目录子目录及所有文件的例子
2014/11/21 Python
Python Json序列化与反序列化的示例
2018/01/31 Python
Python实现合并同一个文件夹下所有txt文件的方法示例
2018/04/26 Python
pytorch使用Variable实现线性回归
2019/05/21 Python
详解python中的index函数用法
2019/08/06 Python
Python 变量的创建过程详解
2019/09/02 Python
python tkinter图形界面代码统计工具(更新)
2019/09/18 Python
python解析命令行参数的三种方法详解
2019/11/29 Python
Python文件时间操作步骤代码详解
2020/04/13 Python
Evisu官方网站:日本牛仔品牌,时尚街头设计风格
2016/12/30 全球购物
Tory Burch德国官网:美国时尚生活品牌
2018/01/03 全球购物
NFL Game Pass欧洲:在线观看NFL比赛直播和点播,以高清质量播放
2018/08/30 全球购物
PHP高级工程师面试问题推荐
2013/01/18 面试题
SQL Server笔试题
2012/01/10 面试题
党员自我评议对照检查材料
2014/09/27 职场文书
2016年领导干部正风肃纪心得体会
2015/10/09 职场文书
2016年八一建军节活动总结
2016/04/05 职场文书
python基础之类方法和静态方法
2021/10/24 Python
Python实现猜拳与猜数字游戏的方法详解
2022/04/06 Python
Three.js实现雪糕地球的使用示例详解
2022/07/07 Javascript