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数据库操作面向对象的优点
Oct 09 PHP
php模板之Phpbean的目录结构
Jan 10 PHP
php学习之数据类型之间的转换介绍
Jun 09 PHP
第七章 php自定义函数实现代码
Dec 30 PHP
php采用ajax数据提交post与post常见方法总结
Nov 10 PHP
分享一段PHP制作的中文拼音首字母工具类
Dec 11 PHP
PHP将HTML转换成文本的实现代码
Jan 21 PHP
php实现专业获取网站SEO信息类实例
Apr 02 PHP
Laravel 5 框架入门(三)
Apr 09 PHP
深入浅析php中sprintf与printf函数的用法及区别
Jan 08 PHP
PHP记录和读取JSON格式日志文件
Jul 07 PHP
PHP类的自动加载与命名空间用法实例分析
Jun 05 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
AM/FM收音机的安装与调试
2021/03/02 无线电
如何在PHP中使用Oracle数据库(1)
2006/10/09 PHP
PHP 日常开发小技巧
2009/09/23 PHP
php printf输出格式使用说明
2010/12/05 PHP
IIS安装Apache伪静态插件的具体操作图文
2013/07/01 PHP
使用PHP编写的SVN类
2013/07/18 PHP
PHP中exec与system用法区别分析
2014/09/22 PHP
PHP 爬取网页的主要方法
2018/07/13 PHP
针对PHP开发安全问题的相关总结
2019/03/22 PHP
TP5(thinkPHP5框架)基于bootstrap实现的单图上传插件用法示例
2019/05/29 PHP
JavaScript 判断判断某个对象是Object还是一个Array
2010/01/28 Javascript
Javascript中的相等与不等运算
2010/04/25 Javascript
基于Jquery的仿照flash放大图片效果代码
2011/03/16 Javascript
根据选择不同的下拉值出现相对应的文本输入框
2013/08/01 Javascript
javascript中的变量作用域以及变量提升详细介绍
2013/10/24 Javascript
JS和JQUERY获取页面大小,滚动条位置,元素位置(示例代码)
2013/12/14 Javascript
JS模式之简单的订阅者和发布者模式完整实例
2015/06/30 Javascript
jQuery中attr()与prop()函数用法实例详解(附用法区别)
2015/12/29 Javascript
基于gulp合并压缩Seajs模块的方式说明
2016/06/14 Javascript
Javascript动画效果(3)
2016/10/11 Javascript
JavaScript 函数节流详解及方法总结
2017/02/09 Javascript
JavaScript继承定义与用法实践分析
2018/05/28 Javascript
vue自定义底部导航栏Tabbar的实现代码
2018/09/03 Javascript
原生js实现公告滚动效果
2021/01/10 Javascript
微信小程序实现单选选项卡切换效果
2020/06/19 Javascript
浅谈layui数据表格判断问题(加入表单元素),设置单元格样式
2019/10/26 Javascript
vant实现购物车功能
2020/06/29 Javascript
利用Python脚本生成sitemap.xml的实现方法
2017/01/31 Python
Python实现通过解析域名获取ip地址的方法分析
2019/05/17 Python
日本AOKI官方商城:AOKI西装
2020/06/11 全球购物
中文师范生自荐信
2014/01/30 职场文书
公交公司毕业生求职信
2014/02/15 职场文书
乡镇党员干部四风对照检查材料思想汇报
2014/09/27 职场文书
护士2014年终工作总结
2014/11/11 职场文书
HTML5页面音频自动播放的实现方式
2021/06/21 HTML / CSS
2021好看的国漫排行榜前十名 《完美世界》上榜,《元龙》排名第一
2022/03/18 国漫