背景:前几天在segmentfault花了两个小时学习了《自己动手造轮子, 实现一个现代的PHP框架》,当然这个课程只能是给学习者提供一个思路,比如说一个框架应该由哪些组件组成,然后再大致讲解下实现每个组件的思路。课程中涉及到依赖注入容器的实现,我之前并没有研究过,所以上课时简直一脸懵圈,正好最近在学习Yii框架,现在就从Yii框架-依赖注入(DI)容器-开始吧!
基本概念
依赖注入(Dependency Injection)
- 降低了依赖与被依赖类型之间的耦合
- 可以单独维护被依赖类型的创建过程
控制反转(Inversion of Control)
- 转移依赖关系 降低耦合
- 依赖抽象类型而非具体类型
依赖注入容器(Dependency Injection Container)
- 管理应用程序中的全局对象
- 自动维护对象之间的依赖关系 (自动注入依赖)
- 可以延迟加载对象(仅用到时才创建对象)。
- 启动容器后,所有对象直接取用
其实我觉得这个依赖注入和控制反转其实是同一个东西,只不过所强调有所不同,IOC强调的是一种降低依赖与被依赖类型之间耦合的模式,而依赖注入则是强调注入这个动作,把被依赖对象从程序中剥离出来,使用动态的注入方式。
代码演示
没有代码的说明,可能对上述概念还会比较模糊,下面先引入问题,再尝试用上述的概念指导改进代码的实现
为什么需要控制反转
假设应用程序有发送短信的需求,如果使用耦合度最高方式,可能是这样的
class Register
{
public $sender;
public function __construct()
{
$this->sender = new Alidayu();
}
public function sendMessage($phone,$content)
{
$this->sender->send($phone,$content);
}
}
class Alidayu
{
public function send($phone,$content)
{
//发送短信
}
}
$register = new Register();
$register->sendMessage('13312341234','注册成功');
当我们不使用阿里大于,而改用其他第三方短信服务时,那我们还需要对Register代码进行改动。显然,Alidayu和Register之间耦合度很高。
控制反转代码实现
依据控制反转的概念,我们在Register中的依赖应当基于抽象,而非基于具体的实现,对上述代码做出修改
Interface Sender
{
public function send($phone,$content);
}
class yunliantong implements Sender
{
public function send($phone,$content)
{
//发送短信
}
}
class Register()
{
public $sender;
public function __construct(Sender $sender)
{
$this->sender = $sender;
}
}
$sender = new yunliantong();
$register = new Register($sender);
$register->sender->send('1331234567','注册成功');
这就是IOC控制反转,从原来的依赖具体类型,到现在的依赖抽象类型,把被依赖的类型解放出来,允许被依赖的类型独立变化,使用的时候再动态的注入依赖的对象中。
现在,你仔细分析,这个控制反转是和依赖注入是否是同一个东西,实现这个控制翻转,是通过依赖注入这个动作来完成的
现在看来,上述代码似乎并没有什么太大问题了。
但如果这个Register依赖的对象有多个,同时这些依赖的对象又依赖了其他的对象,这样的话,代码有可能是下面这样的。
$db = new db();
$user = new user($db);
$Mail = new Mail();
$Mailer = new qqMailer($Mail);
$sender = new yunliantong();
...
如果还有更多的话 。。。
...
$register = new Register($sender,$Mailer,$user);
这样手动的为对象注入依赖,会带来许多不便,因为我们必需要注意创建对象的顺序,那能不能做到自动的注入依赖?注意,是自动,你想到了什么?
对,就是递归地创建依赖的对象。
比如说A依赖B,B依赖C,C无依赖。
现在我直接取出A的实例,系统自动帮我创建好它们之间的依赖关系,流程应该是这样的。
那系统怎么知道A有没有依赖,如果有依赖,那又怎么知道是依赖何方神圣?答案是使用PHP5 的反射机制,详情见php-manual-reflection
反射是指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。
其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言。
为了配合下面的yii DI容器的源码解读,对将使用的类方法进行简单的说明。
使用ReflectionClass返回一个类的内部信息
$reflector = new ReflectionClass('类名称');
使用getConstructor( ) 获取类的构造函数
$constructor = $reflector->getConstrutor();
使用getParameters( ) 获取函数参数
$params = $constructor->getParameters();
使用isDefaultValueAvailable( ) 判断参数是否有默认值
使用getDefaultValue( )获取默认值
使用getClass( )获取参数的暗示的类
foreach($params as $param)
{
if ($param->isDefaultValueAvaliable()) {
echo $param->getDefaultValue();
}else{
echo $param->getClass();
}
}
使用newInstanceArgs 从指定的参数生成类的新实例.
class A
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
$reflector = new ReflectionClass('A');
$object = $reflector->newInstanceArgs(['I AM A']);
print_r($object);//Object ( [name] => I AM A )
小试牛刀
实现用递归注入依赖信息
class simpleDI
{
//依赖
public $_dependencies = [];
//反射
public $_reflection = [];
//获取一个实例
public function get($class)
{
//如果是一个类就实例化
if(class_exists($class)) {
return $this->build($class);
}
throw new