_一个没有绝对答案的世界,却拥有绝对的丰富。 --《沈奇岚:我愿生命从容》_
##2.11.1 定义
###(1) 关于依赖注入
即控制反转,目的是了减少耦合性,简单来说就是使用开放式来获取需要的资源。
###(2) 关于资源
这里说的资源主要是在开发过程中使用到的资源,包括配置项;数据库连接、Memcache、接口请求等系统级的服务;以及业务级使用到的实例等。
引入依赖注入的目的不仅是为了增加一个类,而是为了更好的对资源进行初始化、管理和维护。下面将进行详细的说明。
##2.11.2 一个简单的例子
很多时候,类之间会存在依赖、引用、委托的关系,如A依赖B时,可以这样使用:
```javascript
class A {
protected $_b;
public function __construct()
{
$this->b = new B();
}
}
```
这种方式在A内限制约束了B的实例对象,当改用B的子类或者改变B的构建方式时,A需要作出调整。这时可以通过依赖来改善这种关系:
```javascript
class A {
protected $_b;
public function __construct($b)
{
$this->b = $b;
}
}
```
再进一步,可以使用DI对B的对象进行管理:
```javascript
class A {
public function __construct()
{
}
public function doSth()
{
//当你需要使用B时
$b = $di->get('B');
}
}
```
这样的好处?
一方面,对于使用A的客户(指开发人员),不需要再添加一个B的成员变量,特别不是全部类的成员函数都需要使用B类服务时。另一方面在外部多次初始化A实例时,可以统一对B的构建。
##2.11.3 依赖注入的使用示例
为方便使用,调用的方式有:set/get函数、魔法方法setX/getX、类变量$fdi->X、数组$fdi['X'],初始化的途径有:直接赋值、类名、匿名函数。
```javascript
/** ------------------ 创建与设置 ------------------ **/
//获取DI
$di = DI();
//演示的key
$key = 'demoKey';
/** ------------------ 设置 ------------------ **/
//可赋值的类型:直接赋值、类名赋值、匿名函数
$di->set($key, 'Hello DI!');
$di->set($key, 'Simple');
$di->set($key, function(){
return new Simple();
});
//设置途径:除了上面的set(),你还可以这样赋值
$di->setDemoKey('Hello DI!');
$di->demoKey = 'Hello DI!';
$di['demoKey'] = 'Hello DI!';
/** ------------------ 获取 ------------------ **/
//你可以这样取值
echo $di->get('demoKey'), "\n";
echo $di->getDemoKey(), "\n";
echo $di->demoKey, "\n";
echo $di['demoKey']. "\n";
/**
* 演示类
*/
class Simple
{
public function __construct()
{
}
}
```
##2.11.4 依赖注入的好处
###(1)减少对各个类编写工厂方法以单例获取的开发量
DI相当于一个容器,里面可以放置基本的变量,也可以放置某类服务,甚至是像文件句柄这些的资源。在这容器里面,各个被注册的资源只会存在一份,也就是当被注册的资源为一个实例对象时,其效果就等于单例模式。
因此,保存在DI里面的类,不需要再编写获取单例的代码,直接通过DI获取即可。
例如很多API的服务组件以及其他的一些类,都实现了单例获取的方式。分别如:
微博接口调用:
```javascript
<?php
class Weibo_Api
{
protected static $_instance = null;
public static function getInstance()
{
if (!isset(self::$_instance)) {
self::$_instance = new Weibo_Api();
}
return self::$_instance;
}
//....
}
```
七牛云存储接口调用:
```javascript
class Qiniu_Api {
private static $_instance = null; //实例对象
public static function getInstance()
{
if (self::$_instance ===null) {
self::$_instance = new Qiniu_Api();
}
return self::$_instance;
}
}
```
QQ开放平台接口调用:
```javascript
class QQ_Api {
private static $_instance = null; //实例对象
public static function getInstance()
{
if (self::$_instance ===null) {
self::$_instance = new QQ_Api();
}
return self::$_instance;
}
}
```
如果使用DI对上面这些服务进行管理,则上面三个类乃至其他的类对于单例这块的代码都可以忽略不写。注册代码如下:
```javascript
$di->sStockApi = 'Weibo_Api';
$di->sDioAopi = 'Qiniu_Api';
$di->sShopApi = 'QQ_Api';
```
上面是通过类名来进行延迟加载,但需要各个类提供public的无参数的构造函数。如果各个服务需要进行初始化,可以将初始化的工作放置在onInitialize()函数内,DI在对类实例化时会回调此函数进行初始化。
###(2)统一资源注册,便于后期维护管理
这里引入DI,更多是为了“一处创建,多处使用”, 而不是各自创建,各自使用。
####创建和使用分离
考虑以下场景:假设有这样的业务数据需要缓存机制,所以可注册一个实现缓存机制的实例:
```javascript
$di->set('cache', new FileCache());
```
然后提供给多个客户端使用:
```javascript
$di['cache']->set('indexHtml', $indexContent); //缓存页面
$di['cache']->set('config', $config); //缓存公共配置
$di['cache']->set('artistList', $artistList); //缓存数据
```
当需要切换到MC或者Redis缓存或者多层缓存时,只需要修改对缓存机制的注入即可,如:
```javascript
$di->set('cache', new RedisCache());
```
依赖注入的一个很大的优势就在于可以推迟决策,当需要用到某个对象时,才对其实例化。可以让开发人员在一开始时不必要关注过多的细节实现,同时也给后期的扩展和维护带来极大的方便。
再上一层,假设未来我们需要更高级的缓存服务,那么我们可以在不影响客户端使用的情况下,轻松升级。
未来的可配置化的多级缓存策略
以下是一个模拟的使用场景,但依然对现在的项目有一定的帮助。假设我们现在有一个MC集群的缓存且引入了DI,使用如下:
```javascript
<?php
//初始化
$di = Core_DI::one();
$di->cache = new Memcache();
$di->cache->connect('localhost', 11211);
//不同文件的多处使用 ...
echo $di->cache->get('key');
echo $di->cache->get('key2');
echo $di->cache->get('key3');
...
```
假设现在发现一层缓存存在穿透情况,为保证服务器的稳定性,我们已开发实现了多层缓存策略,并且可以通过简单配置即可实现,只需要对DI容器里面的cache实例进行升级,其他客户端的调用即可马上享受到缓存升级的优质服务。升级涉及改动的代码如下:
```javascript
<?php
//初始化
$di = new Core_DI();
$di->cache = function () {
$ultraFastFrontend = new DataFrontend(array(
"lifetime" => 3600
));
$fastFrontend = new DataFrontend(array(
"lifetime" => 86400
));
$slowFrontend = new DataFrontend(array(
"lifetime" => 604800
));
return new Multiple(array(
new ApcCache($ultraFastFrontend, array(
"prefix" => 'cache',
)),
new MemcacheCache($fastFrontend, array(
"prefix" => 'cache',
"host" => "localhost",
"port" => "11211"
)),
new FileCache($slowFrontend, array(
"prefix" => 'cache',
"cacheDir" => "../app/cache/"
))
));
};
```
备注:关于多级缓存策略,后续会提供源代码和重用库,或者期待读者的分享。
###(3)延迟式加载,提高性能
延迟加载可以通过DI中的类名初始化、匿名函数和参数配置(未实现)三种方式来实现。
延迟加载有时候是非常有必要的,如在初始化项目的配置时,随着配置项的数据增加,服务器的性能也将逐渐受到影响,因为配置的内容可能是硬编码,可能来自于数据库,甚至需要通过接口从后台调用获取, 特别当很多配置项不需要使用时。而此时,支持延时加载将可以达到很好的优化,而不用担心在需要使用的时候忘记了初始化。从而很好的提高服务器性能,提高响应速度。
如对一些耗时的资源先进行匿名函数的初始化:
```
$di['hightResource'] = function() {
//获取返回耗性能的资源
//return $resource;
}
```
###(4)以优雅的方式取代滥用的全局变量
在我看来,PHP里面是不应该使用全局变量(global和$_GLOBALS),更不应该到处使用。
用了DI来管理,即可这样注册:
```javascript
$di->set('debug', true);
```
然后这样使用:
```javascript
$debug = $di->get('debug');
```
也许有人会想:仅仅是换个地方存放变量而已吗?其实是换一种思想使用资源。
以此延伸,DI还可用于改善优化另外两个地方:通过include文件途径对变量的使用和变量的多层传递。
变量的多层传递,通俗来说就是漂洋过海的变量。
##2.11.5 DI思想的来源与推荐参考
[Dependency Injection/Service Location](http://docs.phalconphp.com/en/latest/reference/di.html)
- 欢迎使用PhalApi!
- 接口,从简单开始!
- [1.1]-下载与安装
- [1.2]-创建一个自己的项目
- [1.3]-在线体验
- [1.4]-文档、帮助和官网
- [1.10]-对PhalApi框架的抉择
- [1.11]-快速入门(backup)
- [1.12]-参数规则:接口参数规则配置
- [1.13]-统一的接口请求方式:_sevice=XXX.XXX
- [1.14]-统一的返回格式和结构:ret-data-msg
- [1.15]-数据库操作:基于NotORM的使用及优化
- [1.16]-配置读取:内外网环境配置的完美切换
- [1.17]-日记纪录:简化版的日记接口
- [1.18]-快速函数:人性化的关怀
- [1.19]-DI服务速查:各资源服务一览表
- [1.20]-DB操作:数据库基本操作速查
- [1.21]-类的自动加载:遵循PEAR包的命名规范
- [1.22]-签名验证:自定义签名规则
- [1.23]-请求和响应:GET和POST两者皆可得及超越JSON格式返回
- [1.24]-缓存策略:更灵活地可配置化的多级缓存
- [1.25]-国际化翻译:为走向国际化提前做好翻译准备
- [1.26]-数据安全:数据对称加密方案
- [1.27]-精益开发:更富表现力的Model层和重量级数据获取的应对方案
- [1.28]-COOKIE:对COOKIE原生态的支持及记忆加密升级版
- [1.29]-开放与封闭:多入口和统一初始化
- [1.30]-保持的力量:接口开发最佳实践
- [1.31]-新型计划任务:以接口形式实现的计划任务
- [2.11]-核心思想:DI依赖注入-让资源更可控
- [2.12]-海量数据:可配置的分库分表
- [2.13]-接口调试:在线SQL语句查看与性能优化
- [2.14]-测试驱动开发:意图导向编程下的接口开发
- [2.15]-演进:新型计划任务续篇
- [2.16]-领域驱动设计:应对复杂领域业务的Domain层
- [2.17]-微服务:Api接口服务层
- [2.18]-定制化:资源服务的再实现
- [2.19]-扩展库:可重用的扩展类库
- [2.20]-约定编程:架构明显的编程风格
- [2.21]-服务器统一部署方案简明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益项目和团队建设
- [3.1]-扩展类库:微信开发
- [3.2]-扩展类库:代理模式下phprpc协议的轻松支持
- [3.3]-扩展类库:基于PHPMailer的邮件发送
- [3.4]-扩展类库:优酷开放平台接口调用
- [3.5]-扩展类库:七牛云存储接口调用
- [3.6]-扩展类库:新型计划任务
- [3.8]-扩展类库:用户、会话和第三方登录集成
- [3.9]-扩展类库:swoole支持下的长链接和异步任务实现
- [3.11]-扩展类库:基于FastRoute的快速路由
- [4.2]-开发实战2:模拟优酷开放平台接口项目开发
- [4.3]-开发实战3:一个简单的小型项目开发(奔跑吧兄弟投票活动)
- [5.1]-架构与思想:PhalApi核心设计和思想解读
- [5.2]-杂谈:扯一些PhalApi的前世和今生
- [5.3]-框架总结:术语表和PHP开发建议
- [5.4]-许可
- [5.5]-联系和加入我们
- [5.6]-更新日记
- [5.8]-致框架贡献者:加入PhalApi开源指南
- [6.1]-基于接口查询语言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi视频教程
- 附录1:接口文档参考模板