laravel服務容器-----深刻理解控制反轉(IoC)和依賴注入(DI)

首先你們想想什麼是容器,字面意思就是盛放東西的東西,常見的變量,對象屬性都是容器,一個容器可以裝什麼東西,徹底在於你對這個容器的定義。有的容器不只僅只是存文本,變量,而是對象,屬性,那麼咱們經過這種容器就能夠進行不少高級的功能。php

IoC容器html

IoC容器是laravel的核心,它提供了框架須要的一系列的服務,文檔上稱他爲服務容器,解釋爲一個用於管理類依賴和執行依賴注入的強大工具,聽起來暈暈乎乎的,以後在文檔中找到一片文章:http://laravelacademy.org/post/769.html。我寫這篇文章也是爲了更好的理解什麼 是控制反轉和依賴注入。java

 

依賴的產生----超人和超能力laravel

在面向對象的編程中,總要接觸幾個東西:接口,類,對象。接口是類的原型,一個類必定要遵照實現的接口,對象則是一個類實例化的產物。這樣子說不利於理解,咱們能夠舉一個例子。c++

  •   一個怪物橫行的世界裏,咱們須要超人來打敗怪獸,維護世界和平。

咱們把超人當作一個類: 數據庫

class SuperMan {}

 

咱們能夠想一下,一個超人誕生的時候確定有至少有一個超能力,咱們能夠把這個超能力也抽象爲一個對象,一個超能力確定有多種屬性,以及操做方法,咱們先大體定義一個只有屬性的類,至於方法,咱們能夠到後面進行補充:編程

 

class Power {

    protected $ability;  //能力值

    protected $range;   //能力範圍
    
    public function ____construct($ability,$range) {
        $this->ability = $ability;
        $this->range = $range;
    }
}

 

  這時咱們能夠回去修改一下超人類,讓在超人被建立的時候賦予一個超能力:框架

class SuperMan 
{
    protected $power:
    
    public function __construct() {
         $this->power = new Power(999,999);
    }
}

  這樣子,超人在建立的時候就有了超能力,咱們這個時候能夠發現,超人 和超能力之間如今存在一種依賴,怎麼體現出這個依賴了呢,所謂依賴,就是我若依賴了你,那我就離不開你。在面向對象的編程中,這種依賴隨處可見,少許的依賴並不影響什麼,可是當在一個項目中,這種依賴達到一種量級的時候,那會舉步維艱!編程語言

以前的例子中,超能力類實例化後是一個具體的超能力,可是咱們知道,超人的超能力是多元化的,每種超能力的方法、屬性都有不小的差別,無法經過一種類描述徹底。咱們如今進行修改,咱們假設超人能夠有如下多種超能力:ide

  • 飛行,屬性有:飛行速度、持續飛行時間
  • 蠻力,屬性有:力量值
  • 能量彈,屬性有:傷害值、射擊距離、同時射擊個數

咱們會建立這些類:

class Flight
{
    protected $speed;
    protected $holdtime;
    public function __construct($speed, $holdtime) {}
}

class Force
{
    protected $force;
    public function __construct($force) {}
}

class Shot
{
    protected $atk;
    protected $range;
    protected $limit;
    public function __construct($atk, $range, $limit) {}
}

  那麼此時,超人在建立的時候,咱們會根據須要來進行初始化超能力,以下:

class Superman
{
    protected $power;

    public function __construct()
    {
        $this->power = new Fight(9, 100);
        // $this->power = new Force(45);
        // $this->power = new Shot(99, 50, 2);
        /*
        $this->power = array(
            new Force(45),
            new Shot(99, 50, 2)
        );
        */
    }
}

  咱們須要手動在構造函數或者其餘方法中實例化一系列的類,這樣子真的好嗎?能夠想一想,當不一樣的怪獸侵略地球,咱們須要不一樣超能力的超人更好的戰勝怪獸時,咱們血藥從新改造超人,換句話說,改變超能力的同時,咱們還須要從新建立一個超人。這樣子的效率是否是過低了呢,咱們還有什麼更好的方法呢?

看過鋼鐵俠的人都知道鋼鐵俠能夠隨時更換本身的智能芯片,那麼咱們是否是可讓超人的超能力能夠隨時更換,只須要更換一種裝置或者芯片呢?以前咱們是在超人類中固化了他的超能力初始化的行爲,若是咱們要實現經過更換芯片來改變超人的超能力,那麼就應該將超能力初始化行爲轉爲由外部進行,由外部建立超能力模組,而後植入超人體內一個特定的接口,只要那些超能力模組可以知足這個接口均可以被超人利用,增大超人的能力。這種由外部負責其依賴需求的行爲,咱們能夠稱其爲 「控制反轉(IoC)」。

工廠模式,依賴轉移!

 在實現控制反轉前,咱們能夠思考思考:咱們能夠想到,組件、工具(或者超人的模組),是一種可被生產的玩意兒,生產的地方固然是 「工廠(Factory)」,因而有人就提出了這樣一種模式: 工廠模式。

工廠模式,顧名思義,就是一個類所依賴的外部事物的實例,均可以被一個或多個 「工廠」 建立的這樣一種開發模式,就是 「工廠模式」。

咱們爲了給超人制造超能力模組,咱們建立了一個工廠,它能夠製造各類各樣的模組,且僅須要經過一個方法:

class SuperModuleFactory 
{
     public function makeModule($moduleName, $options) {
          switch ($moduleName) {
            case 'Fight': 
                return new Fight($options[0], $options[1]);
            case 'Force': 
                return new Force($options[0]);
            case 'Shot': 
                return new Shot($options[0], $options[1], $options[2]);
        }
     }
}

  這時候,超人 建立之初就可使用這個工廠:

class Superman
{
    protected $power;

    public function __construct()
    {
        // 初始化工廠
        $factory = new SuperModuleFactory;

        // 經過工廠提供的方法制造須要的模塊
        $this->power = $factory->makeModule('Fight', [9, 100]);
        // $this->power = $factory->makeModule('Force', [45]);
        // $this->power = $factory->makeModule('Shot', [99, 50, 2]);
        /*
        $this->power = array(
            $factory->makeModule('Force', [45]),
            $factory->makeModule('Shot', [99, 50, 2])
        );
        */
    }
}

  能夠看得出,咱們再也不須要在超人初始化之初,去初始化許多第三方類,只需初始化一個工廠類,便可知足需求。但這樣彷佛和之前區別不大,只是沒有那麼多 new 關鍵字。其實咱們稍微改造一下這個類,你就明白,工廠類的真正意義和價值了!

class Superman
{
    protected $power;

    public function __construct(array $modules)
    {
        // 初始化工廠
        $factory = new SuperModuleFactory;

        // 經過工廠提供的方法制造須要的模塊
        foreach ($modules as $moduleName => $moduleOptions) {
            $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
        }
    }
}

// 建立超人
$superman = new Superman([
    'Fight' => [9, 100],
    'Shot' => [99, 50, 2]
]);

  如今,「超人」 的建立再也不依賴任何一個 「超能力」 的類,咱們如若修改了或者增長了新的超能力,只須要針對修改 SuperModuleFactory 便可。擴充超能力的同時再也不須要從新編輯超人的類文件,使得咱們變得很輕鬆。可是,這纔剛剛開始。 

IoC 容器的重要組成 —— 依賴注入

由 「超人」 對 「超能力」 的依賴變成 「超人」 對 「超能力模組工廠」 的依賴後,對付小怪獸們變得更加駕輕就熟。但這也正如你所看到的,依賴並未解除,只是由原來對多個外部的依賴變成了對一個 「工廠」 的依賴。假如工廠出了點麻煩,問題變得就很棘手。其實大多數狀況下,工廠模式已經足夠了。工廠模式的缺點就是:接口未知(即沒有一個很好的契約模型,關於這個我立刻會有解釋)、產生對象類型單一。總之就是,仍是不夠靈活。雖然如此,工廠模式依舊十分優秀,而且適用於絕大多數狀況。

咱們知道,超人依賴的模組,咱們要求有統一的接口,這樣才能和超人身上的注入接口對接,最終起到提高超能力的效果。其實侵略地球的不只僅只有這一點小怪獸,後面還有不少大怪獸,這個時候咱們的工廠好像有點力不從心了,由於每次要新填一個超能力,老是要對工廠進行維修和翻新,至關於增長生產線。是否是感受仍是非常噩夢,下一步就是咱們今天的主要配角 —— DI (依賴注入)

因爲對超能力模組的需求不斷增大,咱們須要集合整個世界的高智商人才,一塊兒解決問題,不該該僅僅只有幾個工廠壟斷負責。不太高智商人才們都很是自負,認爲本身的想法是對的,創造出的超能力模組沒有統一的接口,天然而然沒法被正常使用。這時咱們須要提出一種契約,這樣不管是誰創造出的模組,都符合這樣的接口,天然就可被正常使用。

interface SuperModuleInterface
{
    /**
     * 超能力激活方法
     *
     * 任何一個超能力都得有該方法,並擁有一個參數
     *@param array $target 針對目標,能夠是一個或多個,本身或他人
     */
    public function activate(array $target);
}

  上文中,咱們定下了一個超能力接口(模組規範),全部被建立的模組必須遵照這個契約才能被生產和使用。

可能你們對於在php中出現接口這種東西感到非常驚奇,這種東西不是隻有在java,c++中才有的嗎,其實對於任何一款面向對象的編程語言,接口都是存在而且有很大做用的。由於一個對象,都是由他的模板或者原型---類通過實例化後的一個具體事物,有時候,要實現統一方法但具備不一樣功能或特性的時候,須要不少類,這個時候就須要一個契約,讓你們編寫出能夠隨時替換但又不會產生影響的接口,這種由編程語言自己提出的硬性規範,會增長更多優秀的特性。你們以後就會懂得接口的好處。

在這個模組被提出來以後,高智商的科學家們遵循這個接口,建立了不少類:

/**
 * X-超能量
 */
class XPower implements SuperModuleInterface
{
    public function activate(array $target)
    {
        // 這只是個例子。。具體自行腦補
    }
}

/**
 * 終極炸彈
 */
class UltraBomb implements SuperModuleInterface
{
    public function activate(array $target)
    {
        // 這只是個例子。。具體自行腦補
    }
}

  同時,爲了防止一些科學家不遵照這個接口進行惡意的搗亂,咱們如今要修改以前的超人類:

class Superman
{
    protected $module;

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

  

改造完畢!如今,當咱們初始化 「超人」 類的時候,提供的模組實例必須是一個 SuperModuleInterface 接口的實現。不然就會提示錯誤。

正是因爲超人的創造變得容易,一個超人也就不須要太多的超能力,咱們能夠創造多個超人,並分別注入須要的超能力模組便可。這樣的話,雖然一個超人只有一個超能力,但超人更容易變多,咱們也不怕怪獸啦!

如今有人疑惑了,你要講的依賴注入呢?

其實,上面講的內容,正是依賴注入。

什麼叫作依賴注入?

從本文開頭到如今提出來一系列依賴,只要不是由內部生產(好比初始化、構造函數 __construct 中經過工廠方法、自行手動 new 的),而是由外部以參數或其餘形式注入的,都屬於依賴注入(DI) 。是否是豁然開朗?事實上,就是這麼簡單。下面就是一個典型的依賴注入:

// 超能力模組
$superModule = new XPower;
// 初始化一個超人,並注入一個超能力模組依賴
$superMan = new Superman($superModule);

更爲先進的工廠 —— IoC 容器

剛剛列了一串代碼:

// 超能力模組
$superModule = new XPower;
// 初始化一個超人,並注入一個超能力模組依賴
$superMan = new Superman($superModule);

  如今咱們能發現,這是手動的建立一個超人和超能力模板,而且將模板注入剛剛建立的超人當中,然而,現代社會,應該是高效率的生產,乾淨的車間,完美的自動化裝配!

咱們須要自動化 —— 最多一條指令,千軍萬馬來相見。咱們須要一種高級的生產車間,咱們只須要向生產車間提交一個腳本,工廠便可以經過指令自動化生產。這樣子,當一大堆怪獸來了咱們只須要一條指令就有千軍萬馬來相見。這種更爲高級的工廠,就是工廠模式的昇華 —— IoC 容器。

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

  這時候,一個十分粗糙的容器就誕生了。如今的確很簡陋,但不妨礙咱們進一步提高他。先着眼如今,看看這個容器如何使用吧!

// 建立一個容器(後面稱做超級工廠)
$container = new Container;

// 向該 超級工廠添加超人的生產腳本
$container->bind('superman', function($container, $moduleName) {
    return new Superman($container->make($moduleName));
});

// 向該 超級工廠添加超能力模組的生產腳本
$container->bind('xpower', function($container) {
    return new XPower;
});

// 同上
$container->bind('ultrabomb', function($container) {
    return new UltraBomb;
});

// ****************** 華麗麗的分割線 **********************
// 開始啓動生產
$superman_1 = $container->make('superman', 'xpower');
$superman_2 = $container->make('superman', 'ultrabomb');
$superman_3 = $container->make('superman', 'xpower');
// ...隨意添加

  看到沒?經過最初的 綁定(bind) 操做,咱們向 超級工廠 註冊了一些生產腳本,這些生產腳本在生產指令下達之時便會執行。發現沒有?咱們完全的解除了 超人 與 超能力模組 的依賴關係,更重要的是,容器類也絲毫沒有和他們產生任何依賴!咱們經過註冊、綁定的方式向容器中添加一段能夠被執行的回調(能夠是匿名函數、非匿名函數、類的方法)做爲生產一個類的實例的 腳本 ,只有在真正的 生產(make) 操做被調用執行時,纔會觸發。這樣一種方式,使得咱們更容易在建立一個實例的同時解決其依賴關係,而且更加靈活。當有新的需求,只需另外綁定一個「生產腳本」便可。

實際上,真正的 IoC 容器更爲高級。咱們如今的例子中,仍是須要手動提供超人所須要的模組參數,但真正的 IoC 容器會根據類的依賴需求,自動在註冊、綁定的一堆實例中搜尋符合的依賴需求,並自動注入到構造函數參數中去。Laravel 框架的服務容器正是這麼作的。實現這種功能其實理論上並不麻煩。

如今,到目前爲止,咱們已經再也不害怕怪獸們了。高智商人才集思廣益,層次分明,根據接口契約創造規範的超能力模組。超人開始批量產出。最終,人人都是超人,你也能夠是哦!

從新審視 Laravel 的核心

如今,咱們開始慢慢解讀 Laravel 的核心。其實,Laravel 的核心就是一個 IoC 容器,也剛好是我以前所說的高級的 IoC 容器。

能夠說,Laravel 的核心自己十分輕量,並無什麼很神奇很實質性的應用功能。不少人用到的各類功能模塊好比 Route(路由)、Eloquent ORM(數據庫 ORM 組件)、Request(請求)以及 Response(響應)等等等等,實際上都是與核心無關的類模塊提供的,這些類從註冊到實例化,最終被你所使用,其實都是 Laravel 的服務容器負責的。

咱們以你們最多見的 Route 類做爲例子。你們可能常常見到路由定義是這樣的:

Route::get('/', function() {
    // bla bla bla...
});

  實際上, Route 類被定義在這個命名空間:Illuminate\Routing\Router,文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php,

服務提供者

咱們在前文介紹 IoC 容器的部分中,提到了,一個類須要綁定、註冊至容器中,才能被「製造」。

對,一個類要被容器所可以提取,必需要先註冊至這個容器。既然 Laravel 稱這個容器叫作服務容器,那麼咱們須要某個服務,就得先註冊、綁定這個服務到容器,那麼提供服務並綁定服務至容器的東西,就是服務提供者(Service Provider)。

雖然,綁定一個類到容器不必定非要經過服務提供者。

可是,咱們知道,有時候咱們的類、模塊會有須要其餘類和組件的狀況,爲了保證初始化階段不會出現所須要的模塊和組件沒有註冊的狀況,Laravel 將註冊和初始化行爲進行拆分,註冊的時候就只能註冊,初始化的時候就是初始化。拆分後的產物就是如今的服務提供者。

服務提供者主要分爲兩個部分,register(註冊) 和 boot(引導、初始化),具體參考文檔register 負責進行向容器註冊「腳本」,但要注意註冊部分不要有對未知事物的依賴,若是有,就要移步至 boot 部分。

門面(Facade

咱們如今解答以前關於 Route 的方法爲什麼能以靜態方法訪問的問題。實際上這個問題文檔上有寫,簡單說來就是模擬一個類,提供一個靜態魔術方法__callStatic,並將該靜態方法映射到真正的方法上。

咱們使用的 Route 類其實是 Illuminate\Support\Facades\Route 經過 class_alias() 函數創造的別名而已,這個類被定義在文件 vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php 。

咱們打開文件一看……誒?怎麼只有這麼簡單的一段代碼呢?

 

<?php 
    namespace Illuminate\Support\Facades;

    /**
     * @see \Illuminate\Routing\Router
     */
    class Route extends Facade {

        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor()
        {
            return 'router';
        }

}

  

其實仔細看,會發現這個類繼承了一個叫作 Facade 的類,到這裏謎底差很少要解開了。

上述簡單的定義中,咱們看到了 getFacadeAccessor 方法返回了一個 route,這是什麼意思呢?事實上,這個值被一個 ServiceProvider 註冊過,你們應該知道註冊了個什麼,固然是那個真正的路由類!

本文來自:http://laravelacademy.org/post/769.html

相關文章
相關標籤/搜索