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 相关文章推荐
一个用php3编写的简单计数器
Oct 09 PHP
优化使用mysql存储session的php代码
Jan 10 PHP
php 变量定义方法
Jun 14 PHP
php 分库分表hash算法
Nov 12 PHP
php 截取字符串并以零补齐str_pad() 函数
May 07 PHP
php设计模式 Strategy(策略模式)
Jun 26 PHP
无法在发生错误时创建会话,请检查 PHP 或网站服务器日志,并正确配置 PHP 安装(win+linux)
May 05 PHP
php简单socket服务器客户端代码实例
May 18 PHP
PHP面向对象程序设计类的定义与用法简单示例
Dec 27 PHP
Laravel框架验证码类用法实例分析
Sep 11 PHP
解决windows上php xdebug 无法调试的问题
Feb 19 PHP
php使用gearman进行任务分发操作实例详解
Feb 26 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 array_pop()数组函数将数组最后一个单元弹出(出栈)
2011/07/12 PHP
php操作SVN版本服务器类代码
2011/11/27 PHP
jquery text,radio,checkbox,select操作实现代码
2009/07/09 Javascript
jQuery取得select选择的文本与值的示例
2013/12/09 Javascript
模拟用户点击弹出新页面不会被浏览器拦截
2014/04/08 Javascript
NodeJS制作爬虫全过程
2014/12/22 NodeJs
JS实现滑动菜单效果代码(包括Tab,选项卡,横向等效果)
2015/09/24 Javascript
使用Curl命令查看请求响应时间方法
2016/11/04 Javascript
angularjs2中父子组件的数据传递的实例代码
2017/07/05 Javascript
Element 默认勾选表格 toggleRowSelection的实现
2019/09/04 Javascript
Python cx_freeze打包工具处理问题思路及解决办法
2016/02/13 Python
python如何实现int函数的方法示例
2018/02/19 Python
Python装饰器用法实例总结
2018/05/26 Python
详解django.contirb.auth-认证
2018/07/16 Python
python重试装饰器的简单实现方法
2019/01/31 Python
使用Python-OpenCV向图片添加噪声的实现(高斯噪声、椒盐噪声)
2019/05/28 Python
Python中一些深不见底的“坑”
2019/06/12 Python
浅谈Python_Openpyxl使用(最全总结)
2019/09/05 Python
Python实现数值积分方式
2019/11/20 Python
Python模块的制作方法实例分析
2019/12/21 Python
python实现异常信息堆栈输出到日志文件
2019/12/26 Python
pytorch逐元素比较tensor大小实例
2020/01/03 Python
NumPy统计函数的实现方法
2020/01/21 Python
在echarts中图例legend和坐标系grid实现左右布局实例
2020/05/16 Python
基于Python采集爬取微信公众号历史数据
2020/11/27 Python
python用opencv 图像傅里叶变换
2021/01/04 Python
迪卡侬荷兰官网:Decathlon荷兰
2017/10/29 全球购物
REN Clean Skincare官网:英国本土有机护肤品牌
2019/02/23 全球购物
中文专业毕业生自荐信
2013/10/28 职场文书
机械专业毕业生推荐信范文
2013/11/25 职场文书
党支部意见范文
2015/06/02 职场文书
2019暑假学生安全口号
2019/06/27 职场文书
CSS的class与id常用的命名规则
2021/05/18 HTML / CSS
AngularJS实现多级下拉框
2022/03/25 Javascript
MySQL串行化隔离级别(间隙锁实现)
2022/06/16 MySQL
PostgreSQL之连接失败的问题及解决
2023/05/08 PostgreSQL