PHP程序员如何理解依赖注入容器(dependency injection container)
背景知识
传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim类,接着做些其它工作。
// 代码【1】
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
} class Bar
{
public function doSomething()
{
$bim = new Bim();
$bim->doSomething();
echo __METHOD__, '|';
}
} class Foo
{
public function doSomething()
{
$bar = new Bar();
$bar->doSomething();
echo __METHOD__;
}
} $foo = new Foo();
$foo->doSomething(); //Bim::doSomething|Bar::doSomething|Foo::doSomething
使用依赖注入的思路是应用程序用到Foo类,Foo类需要Bar类,Bar类需要Bim类,那么先创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar类注入,再调用Foo方法,Foo调用Bar方法,接着做些其它工作。
// 代码【2】
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
} class Bar
{
private $bim; public function __construct(Bim $bim)
{
$this->bim = $bim;
} public function doSomething()
{
$this->bim->doSomething();
echo __METHOD__, '|';
}
} class Foo
{
private $bar; public function __construct(Bar $bar)
{
$this->bar = $bar;
} public function doSomething()
{
$this->bar->doSomething();
echo __METHOD__;
}
} $foo = new Foo(new Bar(new Bim()));
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这就是控制反转模式。依赖关系的控制反转到调用链的起点。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如Foo类用到了memcache,可以在不修改Foo类代码的情况下,改用redis。
使用依赖注入容器后的思路是应用程序需要到Foo类,就从容器内取得Foo类,容器创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar注入,应用程序调用Foo方法,Foo调用Bar方法,接着做些其它工作.
总之容器负责实例化,注入依赖,处理依赖关系等工作。
代码演示 依赖注入容器 (dependency injection container)
通过一个最简单的容器类来解释一下,这段代码来自 Twittee
class Container
{
private $s = array(); function __set($k, $c)
{
$this->s[$k] = $c;
} function __get($k)
{
return $this->s[$k]($this);
}
}
这段代码使用了魔术方法,在给不可访问属性赋值时,__set() 会被调用。读取不可访问属性的值时,__get() 会被调用。
$c = new Container(); $c->bim = function () {
return new Bim();
};
$c->bar = function ($c) {
return new Bar($c->bim);
};
$c->foo = function ($c) {
return new Foo($c->bar);
}; // 从容器中取得Foo
$foo = $c->foo;
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这段代码使用了匿名函数
再来一段简单的代码演示一下,容器代码来自simple di container
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.');
}
} IoC::bind('bim', function () {
return new Bim();
});
IoC::bind('bar', function () {
return new Bar(IoC::make('bim'));
});
IoC::bind('foo', function () {
return new Foo(IoC::make('bar'));
}); // 从容器中取得Foo
$foo = IoC::make('foo');
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这段代码使用了后期静态绑定
依赖注入容器 (dependency injection container) 高级功能
真实的dependency injection container会提供更多的特性,如
自动绑定(Autowiring)或 自动解析(Automatic Resolution)
注释解析器(Annotations)
延迟注入(Lazy injection)
下面的代码在Twittee的基础上,实现了Autowiring。
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
} class Bar
{
private $bim; public function __construct(Bim $bim)
{
$this->bim = $bim;
} public function doSomething()
{
$this->bim->doSomething();
echo __METHOD__, '|';
}
} class Foo
{
private $bar; public function __construct(Bar $bar)
{
$this->bar = $bar;
} public function doSomething()
{
$this->bar->doSomething();
echo __METHOD__;
}
} class Container
{
private $s = array(); public function __set($k, $c)
{
$this->s[$k] = $c;
} public function __get($k)
{
// return $this->s[$k]($this);
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);
} /** @var ReflectionClass $reflector */
$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.');
}
} // ----
$c = new Container();
$c->bar = 'Bar';
$c->foo = function ($c) {
return new Foo($c->bar);
};
// 从容器中取得Foo
$foo = $c->foo;
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething // ----
$di = new Container(); $di->foo = 'Foo'; /** @var Foo $foo */
$foo = $di->foo; var_dump($foo);
/*
Foo#10 (1) {
private $bar =>
class Bar#14 (1) {
private $bim =>
class Bim#16 (0) {
}
}
}
*/ $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
以上代码的原理参考PHP官方文档:反射,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。
若想进一步提供一个数组访问接口,如$di->foo可以写成$di'foo'],则需用到[ArrayAccess(数组式访问)接口。
一些复杂的容器会有许多特性,下面列出一些相关的github项目,欢迎补充。
PHP程序员如何理解依赖注入容器(dependency injection container)的更多相关文章
- Srping - bean的依赖注入(Dependency injection)
目录 1 概述 2 两种基本的依赖注入方式 2.1 构造函数方式 2.2Setter方式 3 其他依赖注入功能 3.1 <ref/>标签引用不同范围的bean 3.2 内部bean 3.3 ...
- 清晰架构(Clean Architecture)的Go微服务: 依赖注入(Dependency Injection)
在清晰架构(Clean Architecture)中,应用程序的每一层(用例,数据服务和域模型)仅依赖于其他层的接口而不是具体类型. 在运行时,程序容器¹负责创建具体类型并将它们注入到每个函数中,它使 ...
- 控制反转(Inversion of Control,英文缩写为IoC),另外一个名字叫做依赖注入(Dependency Injection,简称DI)
控制反转(Inversion of Control,英文缩写为IoC),另外一个名字叫做依赖注入(Dependency Injection,简称DI),是一个重要的面向对象编程的法则来削减计算机程序的 ...
- 深度理解依赖注入(Dependence Injection)
前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相当于对那篇文章的解读.所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文:但是,如果您和笔者一样,以前曾经看过,似乎看 ...
- [转]深度理解依赖注入(Dependence Injection)
http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html 前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相 ...
- ASPNET5 依赖注入(Dependency Injection)
依赖注入一直是asp.net web框架(Web API,SignalR and MVC)中不可或缺的一部分,但是在以前,这个框架都是各自升级,都有各自的依赖注入实现方式,即使Katana项目想通过O ...
- 依赖注入(Dependency Injection)
Spring的两个核心内容为控制反转(Ioc)和面向切面(AOP),依赖注入(DI)是控制反转(Ioc)的一种方式. 依赖注入这个词让人望而生畏,现在已经演变成一项复杂的编程技巧 或设计模式理念.但事 ...
- Scalaz(16)- Monad:依赖注入-Dependency Injection By Reader Monad
在上一篇讨论里我们简单的介绍了一下Cake Pattern和Reader Monad是如何实现依赖注入的.主要还是从方法上示范了如何用Cake Pattern和Reader在编程过程中解析依赖和注入依 ...
- 一文读懂Asp.net core 依赖注入(Dependency injection)
一.什么是依赖注入 首先在Asp.net core中是支持依赖注入软件设计模式,或者说依赖注入是asp.net core的核心: 依赖注入(DI)和控制反转(IOC)基本是一个意思,因为说起来谁都离不 ...
随机推荐
- form表单上传文件
一.formData()直接获取form表单数据 例子:获取form表单的id给formData(),然后传给后台. 要求: 传入值的name值必须与后台接受的name相对应. form表单不能嵌套, ...
- iOS8以后UIAlertView和UIActionSheet两种alert页面都将通过UIAlertController来创建
1. Important: UIAlertView is deprecated in iOS 8. (Note that UIAlertViewDelegate is also deprecated. ...
- 不用循环,、es6创建一个长度为100的数组
问题描述:在不使用循环的条件下,如何创建一个长度为100的数组,并且数组的每一个元素是该元素的下标? 结果为: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1 ...
- 基于kafka rest实现资源访问服务化(实战)
问题引出 新产品的体系架构包含多个模块,模块集特点是数量多.模块间交互复杂.那么统一接口是一个很好的解决方案,为了实现统一接口打算采用微服务的核心思想,设计了采用restful service的数据交 ...
- 一点一点看JDK源码(五)java.util.ArrayList 后篇之sort与Comparator
一点一点看JDK源码(五)java.util.ArrayList 后篇之sort与Comparator liuyuhang原创,未经允许禁止转载 本文举例使用的是JDK8的API 目录:一点一点看JD ...
- 二、Shiro 认证开发
I.java开发 环境准备 <dependencies> <dependency> <groupId>junit</groupId> <artif ...
- Null 值对索引排序的影响案例一则
--原SQL 语句如下:select * from (select tmp_tb.*, ROWNUM row_id from (select wpid, customer_id, customer_n ...
- 如何应用SPRITEKIT的CAMERA实现游戏中的ENDLESS无限循环背景
A Tutorial For How To Use SpriteKit Camera Making Endless Background Player运用Camera节点向前移动的效果 向前舞动 命为 ...
- iOS之estimatedHeightForRowAtIndexPath避免程序EXC_BAD_ACCESS
在你的项目中把estimatedHeightForRowAtIndexPath方法写实现以下,返回一个估计高度(随便估,笔者建议还是按照正常思路来写,大概高度是多少就返回多少),这样就不会报EXC_B ...
- JavaScript基础部分经典案例
再复杂的程序都是由一个个简单的部分组成. 001案例 - 交换两个变量的值 方法01 - 使用临时变量 var n1 = 5; var n2 = 6; // 创建一个临时中介变量 tmp var tm ...