依赖注入(DI)在PHP中的实现
什么是依赖注入?
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。
当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。
举个简单的例子:
(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。
(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。
(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。
第一种情况下,实例的调用者创建被调用的实例,必然要求被调用的类出现在调用者的代码里。无法实现二者之间的松耦合。
第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,依赖注入容器自动提供被调用者实例。事实上,调用者和被调用者都处于依赖注入容器的管理下,二者之间的依赖关系由依赖注入容器提供。因此调用者与被调用者的耦合度进一步降低,这使得应用更加容易维护,这就是依赖注入所要达到的目的。
用php实现一个轻量的依赖注入容器
首先我们创建一个类,看起来是这样的:
<?php
class Di
{
protected $_service = [];
public function set($name, $definition)
{
$this->_service[$name] = $definition;
}
public function get($name)
{
if (isset($this->_service[$name])) {
$definition = $this->service[$name];
} else {
throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");
}
if (is_object($definition)) {
$instance = call_user_func($definition);
}
return $instance;
}
}
现在我们已经有了一个简单的类,包含一个属性和两个方法。假设我们现在有两个类,redisDB和cache,redisDB提供一个redis数据库的操作,cache负责缓存功能的实现并且依赖于redisDB。
class redisDB
{
protected $_di;
protected $_options;
public function __construct($options = null)
{
$this->_options = $options;
}
public function setDI($di)
{
$this->_di = $di;
}
public function find($key, $lifetime)
{
// code
}
public function save($key, $value, $lifetime)
{
// code
}
public function delete($key)
{
// code
}
}
在这个类中我们简单实现了redis的查询、保存和删除。你可能会有疑问,另外一个方法setDi是做什么的。待我继续为你讲解。另一个类和当前这个类结构很像:
class cache
{
protected $_di;
protected $_options;
protected $_connect;
public function __construct($options = null)
{
$this->_options = $options;
}
public function setDI($di)
{
$this->_di = $di;
}
protected function _connect()
{
$options = $this->_options;
if (isset($options['connect'])) {
$service = $options['connect'];
} else {
$service = 'redis';
}
return $this->_di->get($service);
}
public function get($key, $lifetime)
{
$connect = $this->_connect;
if (!is_object($connect)) {
$connect = $this->_connect()
$this->_connect = $connect;
}
// code
...
return $connect->find($key, $lifetime);
}
public function save($key, $value, $lifetime)
{
$connect = $this->_connect;
if (!is_object($connect)) {
$connect = $this->_connect()
$this->_connect = $connect;
}
// code
...
return $connect->save($key, $lifetime);
}
public function delete($key)
{
$connect = $this->_connect;
if (!is_object($connect)) {
$connect = $this->_connect()
$this->_connect = $connect;
}
// code
...
$connect->delete($key, $lifetime);
}
}
现在我们就当已经实现了redisDB和cache这两个组件,具体的细节这里就先不做讨论了,来看看如何使用使用吧。首先需要将两个组件注入到容器中:
<?php
$di = new Di();
$di->set('redis', function() {
return new redisDB([
'host' => '127.0.0.1',
'port' => 6379
]);
});
$di->set('cache', function() use ($di) {
$cache = new cache([
'connect' => 'redis'
]);
$cache->setDi($di);
return $cache;
});
// 然后在任何你想使用cache的地方
$cache = $di->get('cache');
$cache->get('key'); // 获取缓存数据
$cache->save('key', 'value', 'lifetime'); // 保存数据
$cache->delete('key'); // 删除数据
到这里你可能会觉得这样以来反而有点繁琐了。cache和redisDB的结构如此之像,完全可以把redis写到cache中而没必要单独分离出来?但是你想过没有,有些数据及时性没那么高而且数量比较大,用redis有点不合适,mongodb是更好的选择;有些数据更新频率更慢,对查询速度也没要求,直接写入文件保存到硬盘可能更为合适;再或者,你的客户觉得redis运维难度有点大,让你给他换成memcache... 这就是为什么把它分离出来了。然后,继续改进代码:
interface BackendInterface {
public function find($key, $lifetime);
public function save($key, $value, $lifetime);
public function delete($key);
}
class redisDB implements BackendInterface
{
public function find($key, $lifetime) { }
public function save($key, $value, $lifetime) { }
public function delete($key) { }
}
class mongoDB implements BackendInterface
{
public function find($key, $lifetime) { }
public function save($key, $value, $lifetime) { }
public function delete($key) { }
}
class file implements BackendInterface
{
public function find($key, $lifetime) { }
public function save($key, $value, $lifetime) { }
public function delete($key) { }
}
$di = new Di();
// redis
$di->set('redis', function() {
return new redisDB([
'host' => '127.0.0.1',
'port' => 6379
]);
});
// mongodb
$di->set('mongo', function() {
return new mongoDB([
'host' => '127.0.0.1',
'port' => 12707
]);
});
// file
$di->set('file', function() {
return new file([
'path' => 'path'
]);
});
// save at redis
$di->set('fastCache', function() use ($di) {
$cache = new cache([
'connect' => 'redis'
]);
$cache->setDi($di);
return $cache;
});
// save at mongodb
$di->set('cache', function() use ($di) {
$cache = new cache([
'connect' => 'mongo'
]);
$cache->setDi($di);
return $cache;
});
// save at file
$di->set('slowCache', function() use ($di) {
$cache = new cache([
'connect' => 'file'
]);
$cache->setDi($di);
return $cache;
});
// 然后在任何你想使用cache的地方
$cache = $di->get('cache');
我们新增加了一个接口BackendInterface,规定了redisDB,mongoDB,file这三个类必须实现这个接口所要求的功能,至于其他锦上添花的功能,随你怎么发挥。而cache的代码,好像没有变,因为cache不需要关心数据是怎么存入数据库或者文件中。而cache的调用者,也不需要关心cache具体是怎么实现的,只要根据接口实现相应的方法就行了。多人协作你会更加受益,你们只需要商定好接口,然后分别实现就行了。
这就是依赖注入的魅力所在了,虽然看似如此简单。
以上代码还可以继续改进,直到你认为无可挑剔为止。比如,redis服务在一个请求中可能会调用多次,而每次调用都会重新创建,这将有损性能。只需扩展一下DI容器就好增加一个参数或增加一个方法,随你。
class Di
{
protected $_service = [];
protected $_sharedService = [];
public function set($name, $definition, $shared = false)
{
if ($shared) {
$this->_sharedService[$name] = $definition;
} else {
$this->_service[$name] = $definition;
}
}
public function get($name) {
if (isset($this->_service[$name])) {
$definition = $this->service[$name];
} else if ($this->_sharedService[$name]) {
$definition = $this->_sharedService[$name];
} else {
throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");
}
...
}
这样以来,如果某个服务在一次请求中要调用多次,你就可以将shared属性设置为true,以减少不必要的浪费。如果你觉得每次在注入时都要setDi有点繁琐,想让他自动setDi,那可以这么做:
interface DiAwareInterface
{
public function setDI($di);
public function getDI();
}
class Di
{
protected $service;
public function set($name, $definition)
{
$this->service[$name] = $definition;
}
public function get($name)
{
...
if (is_object($definition)) {
$instance = call_user_func($definition);
}
// 如果实现了DiAwareInterface这个接口,自动注入
if (is_object($instance)) {
if ($instance instanceof DiAwareInterface) {
$instance->setDI($this);
}
}
return $instance;
}
}
class redisDB implements BackendInterface, DiAwareInterface
{
public function find($key, $lifetime) { }
public function save($key, $value, $lifetime) { }
public function delete($key) { }
}
然后,就可以这样:
$di->set('cache', function() {
return new cache([
'connect' => 'mongo'
]);
});
我们现在所实现的这个DI容器还很简陋,还不支持复杂的注入,你可以继续完善它。
不过,通过这些代码你已经了解什么是依赖在注入了,你可以将这种思想应用到你的项目中,或者着手开发你自己的框架。如果想继续深入学习的话,烦请 click Scene。
完。
原文地址:https://www.jianshu.com/p/cb0693dd8d2e
依赖注入(DI)在PHP中的实现的更多相关文章
- Atitit js中的依赖注入di ioc的实现
Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内 builder 即可..2 Service locator method走ok拦2 Jav ...
- ASP.NET MVC 中 Autofac依赖注入DI 控制反转IOC 了解一下
先简单了解一这个几个 名词的意思. 控制反转(IOC) 依赖注入(DI) 并不是某种技术. 而是一种思想.一种面向对象编程法则 什么是控制反转(IOC)? 什么是依赖注入(DI) 可以点击下面链接 ...
- ADO.NET .net core2.0添加json文件并转化成类注入控制器使用 简单了解 iTextSharp实现HTML to PDF ASP.NET MVC 中 Autofac依赖注入DI 控制反转IOC 了解一下 C# AutoMapper 了解一下
ADO.NET 一.ADO.NET概要 ADO.NET是.NET框架中的重要组件,主要用于完成C#应用程序访问数据库 二.ADO.NET的组成 ①System.Data → DataTable, ...
- 轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)
前言 关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享. 依赖注入和控制反转两个概念让很多初学这迷惑, 觉得玄之又玄,高深莫测. 这里想先说明两点: 依赖注入和控制反转不是高级 ...
- [Android]使用Dagger 2依赖注入 - DI介绍(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...
- 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路
开始学习Spring的时候,对依赖注入(DI)——也叫控制反转(IOC)—— 的理解不是很深刻.随着学习的深入,也逐渐有了自己的认识,在此记录,也希望能帮助其他入门同学更深入地理解Spring.本文不 ...
- 控制反转(Ioc)和依赖注入(DI)
控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基 ...
- 控制反转IOC与依赖注入DI【转】
转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...
- 依赖注入(DI)和控制反转(IOC)
依赖注入(DI)和控制反转(IOC) 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只 ...
- MVC进阶之路:依赖注入(Di)和Ninject
MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...
随机推荐
- python的笔记
np.argsort(x):返回数据x从小到大的索引值,记住是一个索引值.当你想获取从小到大的数据的时候,用到这个 data[::-1]:对数据data进行倒转 a=[0,1,2,3,4,5] a[: ...
- postgresql shell发起select操作报错ERROR: relation "tablename" does not exist
最近安装了一套clourdera manager,其中hive元数据保存在postgresql中,因为今天想看一下hive的元数据信息,就登录了psql,连接到hive元数据库,发起select操作, ...
- iOS - 浅谈LLDB调试器
摘要 LLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功 ...
- iOS-程序启动原理和UIApplication(转载)
一.UIApplication 1.简单介绍 (1)UIApplication对象是应用程序的象征,一个UIApplication对象就代表一个应用程序. (2)每一个应用都有自己的UIApplica ...
- 坐标转换convertRect
// 目标view的直接父viwe 目标view 要转换到的view [self.backgr ...
- CH0201 费解的开关 枚举
正解:枚举 解题报告: 入门傻逼题,思维难度不高代码量极小,非常适合上手 然后傻逼的我第二次看这道题的时候依然没想到解法:D 没有办法,就想着写个笔记好歹记录一下以后多复习几次就记着了趴qwq 就是, ...
- nginx 哈希表数据结构
1.哈希表ngx_hash_t的优势和特点 哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入.索引.删除的时间复杂度都是O(1).这样优秀的时间复杂度是通过将元素的ke ...
- 非极大值抑制(NMS)
非极大值抑制顾名思义就是抑制不是极大值的元素,搜索局部的极大值.这个局部代表的是一个邻域,邻域有两个参数可变,一个是邻域的维数,二是邻域的大小.这里不讨论通用的NMS算法,而是用于在目标检测中提取分数 ...
- 图片放大_css3
.flash_little_img{position:relative;}.flash_little_img{width:500px;height:333px;border:none; margin: ...
- 系列解读Dropout
本文主要介绍Dropout及延伸下来的一些方法,以便更深入的理解. 想要提高CNN的表达或分类能力,最直接的方法就是采用更深的网络和更多的神经元,即deeper and wider.但是,复杂的网络也 ...