_一個沒有絕對答案的世界,卻擁有絕對的豐富。 --《沈奇嵐:我愿生命從容》_
##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:接口文檔參考模板