说这个话题之前先讲一个比较高端的思想--'依赖倒置原则'

"依赖倒置是一种软件设计思想,在传统软件中,上层代码依赖于下层代码,当下层代码有所改动时,上层代码也要相应进行改动,因此维护成本较高。而依赖倒置原则的思想是,上层不应该依赖下层,应依赖接口。意为上层代码定义接口,下层代码实现该接口,从而使得下层依赖于上层接口,降低耦合度,提高系统弹性"

上面的解释有点虚,下面我们以实际代码来解释这个理论

比如有这么条需求,用户注册完成后要发送一封邮件,然后你有如下代码:

先有邮件类'Email.class.php'

class Mail{
public function send()
{
/*这里是如何发送邮件的代码*/
}
}

然后又注册的类'Register.class.php'

class Register{
private $_emailObj; public function doRegister()
{
/*这里是如何注册*/ $this->_emailObj = new Mail();
$this->_emailObj->send();//发送邮件
}
}

然后开始注册

include 'Mail.class.php';
include 'Register.class.php';
$reg = new Register();
$reg->doRegister();

看起来事情很简单,你很快把这个功能上线了,看起来相安无事... xxx天过后,产品人员说发送邮件的不好,要使用发送短信的,然后你说这简单我把'Mail'类改下...

又过了几天,产品人员说发送短信费用太高,还是改用邮件的好...  此时心中一万个草泥马奔腾而过...

这种事情,常常在产品狗身上发生,无可奈何花落去...

以上场景的问题在于,你每次不得不对'Mail'类进行修改,代码复用性很低,高层过度依赖于底层。那么我们就考虑'依赖倒置原则',让底层继承高层制定的接口,高层依赖于接口。

interface Mail
{
public function send();
}
class Email implements Mail()
{
public function send()
{
//发送Email
}
}
class SmsMail implements Mail()
{
public function send()
{
//发送短信
}
}
class Register
{
private $_mailObj; public function __construct(Mail $mailObj)
{
$this->_mailObj = $mailObj;
} public function doRegister()
{
/*这里是如何注册*/
$this->_mailObj->send();//发送信息
}
}

下面开始发送信息

/* 此处省略若干行 */
$reg = new Register();
$emailObj = new Email();
$smsObj = new SmsMail(); $reg->doRegister($emailObj);//使用email发送
$reg->doRegister($smsObj);//使用短信发送
/* 你甚至可以发完邮件再发短信 */

上面的代码解决了'Register'对信息发送类的依赖,使用构造函数注入的方法,使得它只依赖于发送短信的接口,只要实现其接口中的'send'方法,不管你怎么发送都可以。上例就使用了"注入"这个思想,就像注射器一样将一个类的实例注入到另一个类的实例中去,需要用什么就注入什么。当然"依赖倒置原则"也始终贯彻在里面。"注入"不仅可以通过构造函数注入,也可以通过属性注入,上面你可以可以通过一个"setter"来动态为"mailObj"这个属性赋值。

上面看了很多,但是有心的读者可能会发现标题中"从此不再考虑加载顺序"这个字眼,你上面的不还是要考虑加载顺序吗? 不还是先得引入信息发送类,然后在引入注册类,然后再实例化吗? 如果类一多,不照样晕!

确实如此,现实中有许多这样的案例,一开始类就那么多,慢慢的功能越来越多,人员越来越多,编写了很多类,要使用这个类必须先引入那个类,而且一定要确保顺序正确。有这么个例子, "a 依赖于b, b 依赖于c, c 依赖于 d, d 依赖于e", 要获取'a'的实例,你必须依次引入 'e,d,c,b'然后依次进行实例化,老的员工知道这个坑,跳过去了。某天来了个新人,他想实例化'a' 可是一直报错,他都不造咋回事,此时只能看看看'a'的业务逻辑,然后知道要先获取'b'的实例,然后在看'b'的业务逻辑,然后... 一天过去了,他还是没有获取到'a'的实例,然后领导来了...

那这个事情到底是新人的技术低下,还是当时架构人员的水平低下了?

现在切入话题,来实现如何不考虑加载顺序,在实现前就要明白要是不考虑加载顺序就意味着让程序自动进行加载自动进行实例化。类要实例化,只要保证完整的传递给'__construct'函数所必须的参数就OK了,在类中如果要引用其他类,也必须在构造函数中注入,否则调用时仍然会发生错误。那么我们需要一个类,来保存类实例化所需要的参数,依赖的其他类或者对象以及各个类实例化后的引用

该类命名为盒子 'Container.class.php', 其内容如下:

/**
* 依赖注入类
*/
class Container{
/**
*@var array 存储各个类的定义 以类的名称为键
*/
private $_definitions = array(); /**
*@var array 存储各个类实例化需要的参数 以类的名称为键
*/
private $_params = array(); /**
*@var array 存储各个类实例化的引用
*/
private $_reflections = array(); /**
* @var array 各个类依赖的类
*/
private $_dependencies = array(); /**
* 设置依赖
* @param string $class 类、方法 名称
* @param mixed $defination 类、方法的定义
* @param array $params 类、方法初始化需要的参数
*/
public function set($class, $defination = array(), $params = array())
{
$this->_params[$class] = $params;
$this->_definitions[$class] = $this->initDefinition($class, $defination);
} /**
* 获取实例
* @param string $class 类、方法 名称
* @param array $params 实例化需要的参数
* @param array $properties 为实例配置的属性
* @return mixed
*/
public function get($class, $params = array(), $properties = array())
{
if(!isset($this->_definitions[$class]))
{//如果重来没有声明过 则直接创建
return $this->bulid($class, $params, $properties);
} $defination = $this->_definitions[$class]; if(is_callable($defination, true))
{//如果声明是函数
$params = $this->parseDependencies($this->mergeParams($class, $params));
$obj = call_user_func($defination, $this, $params, $properties);
}
elseif(is_array($defination))
{
$originalClass = $defination['class'];
unset($definition['class']); //difinition中除了'class'元素外 其他的都当做实例的属性处理
$properties = array_merge((array)$definition, $properties); //合并该类、函数声明时的参数
$params = $this->mergeParams($class, $params);
if($originalClass === $class)
{//如果声明中的class的名称和关键字的名称相同 则直接生成对象
$obj = $this->bulid($class, $params, $properties);
}
else
{//如果不同则有可能为别名 则从容器中获取
$obj = $this->get($originalClass, $params, $properties);
}
}
elseif(is_object($defination))
{//如果是个对象 直接返回
return $defination;
}
else
{
throw new Exception($class . ' 声明错误!');
}
return $obj;
} /**
* 合并参数
* @param string $class 类、函数 名称
* @param array $params 参数
* @return array
*/
protected function mergeParams($class, $params = array())
{
if(empty($this->_params[$class]))
{
return $params;
}
if(empty($params))
{
return $this->_params;
} $result = $this->_params[$class];
foreach($params as $key => $value)
{
$result[$key] = $value;
}
return $result;
} /**
* 初始化声明
* @param string $class 类、函数 名称
* @param array $defination 类、函数的定义
* @return mixed
*/
protected function initDefinition($class, $defination)
{
if(empty($defination))
{
return array('class' => $class);
}
if(is_string($defination))
{
return array('class' => $defination);
}
if(is_callable($defination) || is_object($defination))
{
return $defination;
}
if(is_array($defination))
{
if(!isset($defination['class']))
{
$definition['class'] = $class;
}
return $defination;
}
throw new Exception($class. ' 声明错误');
} /**
* 创建类实例、函数
* @param string $class 类、函数 名称
* @param array $params 初始化时的参数
* @param array $properties 属性
* @return mixed
*/
protected function bulid($class, $params, $properties)
{
list($reflection, $dependencies) = $this->getDependencies($class); foreach ((array)$params as $index => $param)
{//依赖不仅有对象的依赖 还有普通参数的依赖
$dependencies[$index] = $param;
} $dependencies = $this->parseDependencies($dependencies, $reflection); $obj = $reflection->newInstanceArgs($dependencies); if(empty($properties))
{
return $obj;
} foreach ((array)$properties as $name => $value)
{
$obj->$name = $value;
} return $obj;
} /**
* 获取依赖
* @param string $class 类、函数 名称
* @return array
*/
protected function getDependencies($class)
{
if(isset($this->_reflections[$class]))
{//如果已经实例化过 直接从缓存中获取
return array($this->_reflections[$class], $this->_dependencies[$class]);
} $dependencies = array();
$ref = new ReflectionClass($class);//获取对象的实例
$constructor = $ref->getConstructor();//获取对象的构造方法
if($constructor !== null)
{//如果构造方法有参数
foreach($constructor->getParameters() as $param)
{//获取构造方法的参数
if($param->isDefaultValueAvailable())
{//如果是默认 直接取默认值
$dependencies[] = $param->getDefaultValue();
}
else
{//将构造函数中的参数实例化
$temp = $param->getClass();
$temp = ($temp === null ? null : $temp->getName());
$temp = Instance::getInstance($temp);//这里使用Instance 类标示需要实例化 并且存储类的名字
$dependencies[] = $temp;
}
}
}
$this->_reflections[$class] = $ref;
$this->_dependencies[$class] = $dependencies;
return array($ref, $dependencies);
} /**
* 解析依赖
* @param array $dependencies 依赖数组
* @param array $reflection 实例
* @return array $dependencies
*/
protected function parseDependencies($dependencies, $reflection = null)
{
foreach ((array)$dependencies as $index => $dependency)
{
if($dependency instanceof Instance)
{
if ($dependency->id !== null)
{
$dependencies[$index] = $this->get($dependency->id);
}
elseif($reflection !== null)
{
$parameters = $reflection->getConstructor()->getParameters();
$name = $parameters[$index]->getName();
$class = $reflection->getName();
throw new Exception('实例化类 ' . $class . ' 时缺少必要参数:' . $name);
}
}
}
return $dependencies;
}
}

下面是'Instance'类的内容,该类主要用于记录类的名称,标示是否需要获取实例

class Instance{
/**
* @var 类唯一标示
*/
public $id; /**
* 构造函数
* @param string $id 类唯一ID
* @return void
*/
public function __construct($id)
{
$this->id = $id;
} /**
* 获取类的实例
* @param string $id 类唯一ID
* @return Object Instance
*/
public static function getInstance($id)
{
return new self($id);
}
}

然后我们在'Container.class.php'中还是实现了为类的实例动态添加属性的功能,若要动态添加属性,需使用魔术方法'__set'来实现,因此所有使用依赖加载的类需要实现该方法,那么我们先定义一个基础类 'Base.class.php',内容如下

class Base{
/**
* 魔术方法
* @param string $name
* @param string $value
* @return void
*/
public function __set($name, $value)
{
$this->{$name} = $value;
}
}

然后我们来实现'A,B,C'类,A类的实例 依赖于 B类的实例,B类的实例依赖于C类的实例

'A.class.php'

class A extends Base{
private $instanceB; public function __construct(B $instanceB)
{
$this->instanceB = $instanceB;
} public function test()
{
$this->instanceB->test();
}
}

'B.class.php'

class B  extends Base{
private $instanceC; public function __construct(C $instanceC)
{
$this->instanceC = $instanceC;
} public function test()
{
return $this->instanceC->test();
}
}

'C.class.php'

class C  extends Base{
public function test()
{
echo 'this is C!';
}
}de

然后我们在'index.php'中获取'A'的实例,要实现自动加载,需要使用SPL类库的'spl_autoload_register'方法,代码如下

function autoload($className)
{
include_once $className . '.class.php';
}
spl_autoload_register('autoload', true, true);
$container = new Container; $a = $container->get('A');
$a->test();//输出 'this is C!'

上面的例子看起来是不是很爽,根本都不需要考虑'B','C' (当然,这里B,C 除了要使用相应类的实例外,没有其他参数,如果有其他参数,必须显要调用'$container->set(xx)'方法进行注册,为其制定实例化必要的参数)。有细心同学可能会思考,比如我在先获取了'A'的实例,我在另外一个地方也要获取'A'的实例,但是这个地方'A'的实例需要其中某个属性不一样,我怎么做到?

你可以看到'Container' 类的 'get' 方法有其他两个参数,'$params' 和 '$properties' , 这个'$properties' 即可实现刚刚的需求,这都依赖'__set'魔术方法,当然这里你不仅可以注册类,也可以注册方法或者对象,只是注册方法时要使用回调函数,例如

$container->set('foo', function($container, $params, $config){
print_r($params);
print_r($config);
}); $container->get('foo', array('name' => 'foo'), array('key' => 'test'));

还可以注册一个对象的实例,例如

class Test
{
public function mytest()
{
echo 'this is a test';
}
} $container->set('testObj', new Test()); $test = $container->get('testObj');
$test->mytest();

以上自动加载,依赖控制的大体思想就是将类所要引用的实例通过构造函数注入到其内部,在获取类的实例的时候通过PHP内建的反射解析构造函数的参数对所需要的类进行加载,然后进行实例化,并进行缓存以便在下次获取时直接从内存取得

以上代码仅仅用于学习和实验,未经严格测试,请不要用于生产环境,以免产生未知bug

PHP 依赖注入 (转)的更多相关文章

  1. webapi - 使用依赖注入

    本篇将要和大家分享的是webapi中如何使用依赖注入,依赖注入这个东西在接口中常用,实际工作中也用的比较频繁,因此这里分享两种在api中依赖注入的方式Ninject和Unity:由于快过年这段时间打算 ...

  2. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  3. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  4. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  5. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

  6. ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

    在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...

  7. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  8. 模拟AngularJS之依赖注入

    一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...

  9. angular2系列教程(八)In-memory web api、HTTP服务、依赖注入、Observable

    大家好,今天我们要讲是angular2的http功能模块,这个功能模块的代码不在angular2里面,需要我们另外引入: index.html <script src="lib/htt ...

  10. angularjs 依赖注入--自己学着实现

    在用angular依赖注入时,感觉很好用,他的出现是 为了"削减计算机程序的耦合问题" ,我怀着敬畏与好奇的心情,轻轻的走进了angular源码,看看他到底是怎么实现的,我也想写个 ...

随机推荐

  1. 【HTML5】Web Workers

    什么是 Web Worker? 当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成. web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性 ...

  2. php生成二维码的插件phpqrcode

    参考网址: http://www.thinkphp.cn/topic/7749.html http://blog.csdn.net/stxyc/article/details/44650971 php ...

  3. Hark的数据结构与算法练习之鸡尾酒排序

    算法说明 鸡尾酒排序又叫定向冒泡排序,鸡尾酒搅拌排序,搅拌排序,涟漪排序,回来排序,快乐小时排序. 鸡尾酒排序是交换排序的一种,它是冒泡排序的一个轻微的变种.冒泡是从低向高比较排序,鸡尾酒从低向高,从 ...

  4. Kali Linux 2016.2发布提供虚拟机以及系统镜像下载

    Kali Linux 2016.2发布提供虚拟机以及系统镜像下载   Kali Linux 2016.2发布提供虚拟机以及系统镜像下载,本次Kali Linux 2016.2提供了五种桌面模式,分别为 ...

  5. Google地图接口API之Google地图 API 参考手册(七)

    Google 地图API 参考手册 地图 构造函数/对象 描述 Map() 在指定的 HTML 容器中创建新的地图,该容器通常是一个DIV元素. 叠加层 构造函数/对象 描述 Marker 创建一个标 ...

  6. BZOJ3784 : 树上的路径

    树的点分治,在分治的时候将所有点到根的距离依次放入一个数组q中. 对于一棵子树里的点,合法的路径一定是q[L]..q[R]的某个数加上自己到重心的距离. 定义五元组(v,l,m,r,w),表示当前路径 ...

  7. javascript继承扩展类方法实现

    javascript没有原生的继承语法,这确实很让人困惑,但是广大人民群从的智慧是无穷的.最近呢,正尝到一点从源码中学习的甜头,不分享一下实在难以平复激动的心情.前不久改造视频播放插件的时候,找到了v ...

  8. 发送JS错误日志到服务器

    JS记录错误日志/捕捉错误 //onerror提供异常信息,文件路径和发生错误代码的行数的三个参数. window.onerror = function(e,url,index){ var msg = ...

  9. [开源框架推荐]Icepdf:纯java的pdf文档的提取和转换库

    ICEpdf 是一个轻量级的开源 Java 语言的 PDF 类库.通过 ICEpdf 可以用来浏览.内容提取和转换 PDF 文档,而无须一些本地PDF库的支持. 可以用来做什么? 1.从pdf文件中提 ...

  10. 微信企业号办公系统-JSSDK上传图片(多图上传)

    在开发微信企业号办公系统中,涉及到了图片上传功能,一开始使用的flash插件上传方法,在苹果手机上可以调用相机直接拍摄照片,但在安卓手机上只能选择照片. 微信jssdk-api带有一套完整的调用选择本 ...