核心思想:Di依賴注入 讓資源更可控

2018-11-21 21:19 更新

一個(gè)沒有絕對(duì)答案的世界,卻擁有絕對(duì)的豐富。 --《沈奇嵐:我愿生命從容》

2.11.1 定義

(1) 關(guān)于依賴注入

即控制反轉(zhuǎn),目的是了減少耦合性,簡單來說就是使用開放式來獲取需要的資源。

(2) 關(guān)于資源

這里說的資源主要是在開發(fā)過程中使用到的資源,包括配置項(xiàng);數(shù)據(jù)庫連接、Memcache、接口請(qǐng)求等系統(tǒng)級(jí)的服務(wù);以及業(yè)務(wù)級(jí)使用到的實(shí)例等。

引入依賴注入的目的不僅是為了增加一個(gè)類,而是為了更好的對(duì)資源進(jìn)行初始化、管理和維護(hù)。下面將進(jìn)行詳細(xì)的說明。

2.11.2 一個(gè)簡單的例子

很多時(shí)候,類之間會(huì)存在依賴、引用、委托的關(guān)系,如A依賴B時(shí),可以這樣使用:

class A {
    protected $_b;

    public function __construct()
    {
        $this->b = new B();      
    }
}

這種方式在A內(nèi)限制約束了B的實(shí)例對(duì)象,當(dāng)改用B的子類或者改變B的構(gòu)建方式時(shí),A需要作出調(diào)整。這時(shí)可以通過依賴來改善這種關(guān)系:

class A {
    protected $_b;

    public function __construct($b)
    {
        $this->b = $b;       
    }
}

再進(jìn)一步,可以使用DI對(duì)B的對(duì)象進(jìn)行管理:

class A {
    public function __construct()
    {       
    }

    public function doSth()
    {
        //當(dāng)你需要使用B時(shí)
        $b = $di->get('B');
    }
}

這樣的好處?

一方面,對(duì)于使用A的客戶(指開發(fā)人員),不需要再添加一個(gè)B的成員變量,特別不是全部類的成員函數(shù)都需要使用B類服務(wù)時(shí)。另一方面在外部多次初始化A實(shí)例時(shí),可以統(tǒng)一對(duì)B的構(gòu)建。

2.11.3 依賴注入的使用示例

為方便使用,調(diào)用的方式有:set/get函數(shù)、魔法方法setX/getX、類變量$fdi->X、數(shù)組$fdi['X'],初始化的途徑有:直接賦值、類名、匿名函數(shù)。

/** ------------------ 創(chuàng)建與設(shè)置 ------------------ **/
//獲取DI
$di = DI();
//演示的key
$key = 'demoKey';

/** ------------------ 設(shè)置 ------------------ **/
//可賦值的類型:直接賦值、類名賦值、匿名函數(shù)
$di->set($key, 'Hello DI!');
$di->set($key, 'Simple');
$di->set($key, function(){
    return new Simple();
});
//設(shè)置途徑:除了上面的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)減少對(duì)各個(gè)類編寫工廠方法以單例獲取的開發(fā)量

DI相當(dāng)于一個(gè)容器,里面可以放置基本的變量,也可以放置某類服務(wù),甚至是像文件句柄這些的資源。在這容器里面,各個(gè)被注冊(cè)的資源只會(huì)存在一份,也就是當(dāng)被注冊(cè)的資源為一個(gè)實(shí)例對(duì)象時(shí),其效果就等于單例模式。

因此,保存在DI里面的類,不需要再編寫獲取單例的代碼,直接通過DI獲取即可。

例如很多API的服務(wù)組件以及其他的一些類,都實(shí)現(xiàn)了單例獲取的方式。分別如:

微博接口調(diào)用:

<?php
class Weibo_Api
{
    protected static $_instance = null;

    public static function getInstance()
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new Weibo_Api();
        }
        return self::$_instance;
    }

    //....
}

七牛云存儲(chǔ)接口調(diào)用:

class Qiniu_Api {
    private static $_instance = null; //實(shí)例對(duì)象

    public static function getInstance()
    {
        if (self::$_instance ===null) {
            self::$_instance = new Qiniu_Api();
        }
        return self::$_instance;
    }
}

QQ開放平臺(tái)接口調(diào)用:

class QQ_Api { 
    private static $_instance = null; //實(shí)例對(duì)象

    public static function getInstance()
    {
        if (self::$_instance ===null) {
            self::$_instance = new QQ_Api();
        }
        return self::$_instance;
    }
}

如果使用DI對(duì)上面這些服務(wù)進(jìn)行管理,則上面三個(gè)類乃至其他的類對(duì)于單例這塊的代碼都可以忽略不寫。注冊(cè)代碼如下:

$di->sStockApi = 'Weibo_Api';
$di->sDioAopi = 'Qiniu_Api';
$di->sShopApi = 'QQ_Api';

上面是通過類名來進(jìn)行延遲加載,但需要各個(gè)類提供public的無參數(shù)的構(gòu)造函數(shù)。如果各個(gè)服務(wù)需要進(jìn)行初始化,可以將初始化的工作放置在onInitialize()函數(shù)內(nèi),DI在對(duì)類實(shí)例化時(shí)會(huì)回調(diào)此函數(shù)進(jìn)行初始化。

(2)統(tǒng)一資源注冊(cè),便于后期維護(hù)管理

這里引入DI,更多是為了“一處創(chuàng)建,多處使用”, 而不是各自創(chuàng)建,各自使用。

創(chuàng)建和使用分離

考慮以下場景:假設(shè)有這樣的業(yè)務(wù)數(shù)據(jù)需要緩存機(jī)制,所以可注冊(cè)一個(gè)實(shí)現(xiàn)緩存機(jī)制的實(shí)例:

$di->set('cache', new FileCache());

然后提供給多個(gè)客戶端使用:

$di['cache']->set('indexHtml', $indexContent);   //緩存頁面
$di['cache']->set('config', $config);  //緩存公共配置
$di['cache']->set('artistList', $artistList);   //緩存數(shù)據(jù)

當(dāng)需要切換到MC或者Redis緩存或者多層緩存時(shí),只需要修改對(duì)緩存機(jī)制的注入即可,如:

$di->set('cache', new RedisCache());

依賴注入的一個(gè)很大的優(yōu)勢就在于可以推遲決策,當(dāng)需要用到某個(gè)對(duì)象時(shí),才對(duì)其實(shí)例化。可以讓開發(fā)人員在一開始時(shí)不必要關(guān)注過多的細(xì)節(jié)實(shí)現(xiàn),同時(shí)也給后期的擴(kuò)展和維護(hù)帶來極大的方便。

再上一層,假設(shè)未來我們需要更高級(jí)的緩存服務(wù),那么我們可以在不影響客戶端使用的情況下,輕松升級(jí)。
未來的可配置化的多級(jí)緩存策略

以下是一個(gè)模擬的使用場景,但依然對(duì)現(xiàn)在的項(xiàng)目有一定的幫助。假設(shè)我們現(xiàn)在有一個(gè)MC集群的緩存且引入了DI,使用如下:

<?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');
...

假設(shè)現(xiàn)在發(fā)現(xiàn)一層緩存存在穿透情況,為保證服務(wù)器的穩(wěn)定性,我們已開發(fā)實(shí)現(xiàn)了多層緩存策略,并且可以通過簡單配置即可實(shí)現(xiàn),只需要對(duì)DI容器里面的cache實(shí)例進(jìn)行升級(jí),其他客戶端的調(diào)用即可馬上享受到緩存升級(jí)的優(yōu)質(zhì)服務(wù)。升級(jí)涉及改動(dòng)的代碼如下:

<?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/"
    ))
));
};

備注:關(guān)于多級(jí)緩存策略,后續(xù)會(huì)提供源代碼和重用庫,或者期待讀者的分享。

(3)延遲式加載,提高性能

延遲加載可以通過DI中的類名初始化、匿名函數(shù)和參數(shù)配置(未實(shí)現(xiàn))三種方式來實(shí)現(xiàn)。

延遲加載有時(shí)候是非常有必要的,如在初始化項(xiàng)目的配置時(shí),隨著配置項(xiàng)的數(shù)據(jù)增加,服務(wù)器的性能也將逐漸受到影響,因?yàn)榕渲玫膬?nèi)容可能是硬編碼,可能來自于數(shù)據(jù)庫,甚至需要通過接口從后臺(tái)調(diào)用獲取, 特別當(dāng)很多配置項(xiàng)不需要使用時(shí)。而此時(shí),支持延時(shí)加載將可以達(dá)到很好的優(yōu)化,而不用擔(dān)心在需要使用的時(shí)候忘記了初始化。從而很好的提高服務(wù)器性能,提高響應(yīng)速度。

如對(duì)一些耗時(shí)的資源先進(jìn)行匿名函數(shù)的初始化:

$di['hightResource'] = function() {
    //獲取返回耗性能的資源
    //return $resource; 
}

(4)以優(yōu)雅的方式取代濫用的全局變量

在我看來,PHP里面是不應(yīng)該使用全局變量(global和$_GLOBALS),更不應(yīng)該到處使用。

用了DI來管理,即可這樣注冊(cè):

$di->set('debug', true);

然后這樣使用:

$debug = $di->get('debug');

也許有人會(huì)想:僅僅是換個(gè)地方存放變量而已嗎?其實(shí)是換一種思想使用資源。

以此延伸,DI還可用于改善優(yōu)化另外兩個(gè)地方:通過include文件途徑對(duì)變量的使用和變量的多層傳遞。

變量的多層傳遞,通俗來說就是漂洋過海的變量。

2.11.5 DI思想的來源與推薦參考

Dependency Injection/Service Location

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)