溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么用php實現(xiàn)一個輕量的依賴注入容器

發(fā)布時間:2021-09-03 19:27:39 來源:億速云 閱讀:108 作者:chen 欄目:編程語言

本篇內(nèi)容主要講解“怎么用php實現(xiàn)一個輕量的依賴注入容器”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“怎么用php實現(xiàn)一個輕量的依賴注入容器”吧!

什么是依賴注入?

IOC:英文全稱:Inversion of Control,中文名稱:控制反轉(zhuǎn),它還有個名字叫依賴注入(Dependency Injection,簡稱DI)。

當(dāng)一個類的實例需要另一個類的實例協(xié)助時,在傳統(tǒng)的程序設(shè)計過程中,通常由調(diào)用者來創(chuàng)建被調(diào)用者的實例。而采用依賴注入的方式,創(chuàng)建被調(diào)用者的工作不再由調(diào)用者來完成,因此叫控制反轉(zhuǎn),創(chuàng)建被調(diào)用者的實例的工作由IOC容器來完成,然后注入調(diào)用者,因此也稱為依賴注入。

舉個簡單的例子:

(1)原始社會里,幾乎沒有社會分工。需要斧子的人(調(diào)用者)只能自己去磨一把斧子(被調(diào)用者)。

(2)進入工業(yè)社會,工廠出現(xiàn)。斧子不再由普通人完成,而在工廠里被生產(chǎn)出來,此時需要斧子的人(調(diào)用者)找到工廠,購買斧子,無須關(guān)心斧子的制造過程。

(3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發(fā)出一個簡單指令:需要斧子。斧子就自然出現(xiàn)在他面前。

第一種情況下,實例的調(diào)用者創(chuàng)建被調(diào)用的實例,必然要求被調(diào)用的類出現(xiàn)在調(diào)用者的代碼里。無法實現(xiàn)二者之間的松耦合。

第二種情況下,調(diào)用者無須關(guān)心被調(diào)用者具體實現(xiàn)過程,只需要找到符合某種標(biāo)準(接口)的實例,即可使用。此時調(diào)用的代碼面向接口編程,可以讓調(diào)用者和被調(diào)用者解耦,這也是工廠模式大量使用的原因。但調(diào)用者需要自己定位工廠,調(diào)用者與特定工廠耦合在一起。

第三種情況下,調(diào)用者無須自己定位工廠,程序運行到需要被調(diào)用者時,依賴注入容器自動提供被調(diào)用者實例。事實上,調(diào)用者和被調(diào)用者都處于依賴注入容器的管理下,二者之間的依賴關(guān)系由依賴注入容器提供。因此調(diào)用者與被調(diào)用者的耦合度進一步降低,這使得應(yīng)用更加容易維護,這就是依賴注入所要達到的目的。

用php實現(xiàn)一個輕量的依賴注入容器

首先我們創(chuàng)建一個類,看起來是這樣的:

<?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;
    }
}

現(xiàn)在我們已經(jīng)有了一個簡單的類,包含一個屬性和兩個方法。假設(shè)我們現(xiàn)在有兩個類,redisDB和cache,redisDB提供一個redis數(shù)據(jù)庫的操作,cache負責(zé)緩存功能的實現(xiàn)并且依賴于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
    }
}

在這個類中我們簡單實現(xiàn)了redis的查詢、保存和刪除。你可能會有疑問,另外一個方法setDi是做什么的。待我繼續(xù)為你講解。另一個類和當(dāng)前這個類結(jié)構(gòu)很像:

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);
    }
}

現(xiàn)在我們就當(dāng)已經(jīng)實現(xiàn)了redisDB和cache這兩個組件,具體的細節(jié)這里就先不做討論了,來看看如何使用使用吧。首先需要將兩個組件注入到容器中:

<?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'); // 獲取緩存數(shù)據(jù)
    $cache->save('key', 'value', 'lifetime'); // 保存數(shù)據(jù)
    $cache->delete('key'); // 刪除數(shù)據(jù)

到這里你可能會覺得這樣以來反而有點繁瑣了。cache和redisDB的結(jié)構(gòu)如此之像,完全可以把redis寫到cache中而沒必要單獨分離出來?

但是你想過沒有,有些數(shù)據(jù)及時性沒那么高而且數(shù)量比較大,用redis有點不合適,mongodb是更好的選擇;有些數(shù)據(jù)更新頻率更慢,對查詢速度也沒要求,直接寫入文件保存到硬盤可能更為合適;再或者,你的客戶覺得redis運維難度有點大,讓你給他換成memcache… 這就是為什么把它分離出來了。然后,繼續(xù)改進代碼:

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,規(guī)定了redisDB,mongoDB,file這三個類必須實現(xiàn)這個接口所要求的功能,至于其他錦上添花的功能,隨你怎么發(fā)揮。而cache的代碼,好像沒有變,因為cache不需要關(guān)心數(shù)據(jù)是怎么存入數(shù)據(jù)庫或者文件中。而cache的調(diào)用者,也不需要關(guān)心cache具體是怎么實現(xiàn)的,只要根據(jù)接口實現(xiàn)相應(yīng)的方法就行了。多人協(xié)作你會更加受益,你們只需要商定好接口,然后分別實現(xiàn)就行了。

這就是依賴注入的魅力所在了,雖然看似如此簡單。

以上代碼還可以繼續(xù)改進,直到你認為無可挑剔為止。比如,redis服務(wù)在一個請求中可能會調(diào)用多次,而每次調(diào)用都會重新創(chuàng)建,這將有損性能。只需擴展一下DI容器就好增加一個參數(shù)或增加一個方法,隨你。

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

這樣以來,如果某個服務(wù)在一次請求中要調(diào)用多次,你就可以將shared屬性設(shè)置為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);
        }

        // 如果實現(xiàn)了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'
    ]);
});

我們現(xiàn)在所實現(xiàn)的這個DI容器還很簡陋,還不支持復(fù)雜的注入,你可以繼續(xù)完善它。

到此,相信大家對“怎么用php實現(xiàn)一個輕量的依賴注入容器”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI