理解依賴注入與控制反轉

依賴注入與控制反轉

依賴注入 當我第一次接觸這個詞的時候,我是有些丈二和尚摸不着頭腦的,至今我也是感到比較困惑的,所以今天我們來探索一下 Laravel 中的依賴注入(dependency injection), 來好好的理解它。
控制反轉 第一印象是好深奧的名詞。。。看上去好像是說反向控制?不懂?那就理順之!
起點
什麼是依賴

沒有你我就活不下去,那麼,你就是我的依賴。
說白了就是:

    不是我自身的,卻是我需要的,都是我所依賴的。一切需要外部提供的,都是需要進行依賴注入的。

我們用代碼來描述一下:

class Boy {
  protected $girl;

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

class Girl {
  ...
}

$boy = new Boy();  // Error; Boy must have girlfriend!

// so 必須要給他一個女朋友才行
$girl = new Girl();

$boy = new Boy($girl); // Right! So Happy!

從上述代碼我們可以看到 Boy 強依賴 Girl 必須在構造時注入 Girl 的實例才行。

那麼爲什麼要有依賴注入這個概念,依賴注入到底解決了什麼問題?

我們將上述代碼修正一下我們初學時都寫過的代碼:

class Boy {
  protected $girl;

  public function __construct() {
    $this->girl = new Girl();
  }
}

這種方式與前面的方式有什麼不同呢?

我們會發現 Boy 的女朋友被我們硬編碼到 Boy 的身體裏去了。。。 每次 Boy 重生自己想換個類型的女朋友都要把自己扒光才行。。。 (⊙o⊙)…

某天 Boy 特別喜歡一個 LoliGirl , 非常想讓她做自己的女朋友。。。怎麼辦?
重生自己。。。扒開自己。。。把 Girl 扔了。。。把 LoliGirl 塞進去。。。

class LoliGirl {

}

class Boy {
  protected $girl;

  public function __construct() {
      //  $this->girl = new Girl();  // sorry...
      $this->girl = new LoliGirl();
  }
}

某天 Boy 迷戀上了御姐.... (⊙o⊙)… Boy 好煩。。。

是不是感覺不太好?每次遇到真心相待的人卻要這麼的折磨自己。。。

Boy 說,我要變的強大一點。我不想被改來改去的!

好吧,我們讓 Boy 強大一點:

interface Girl {
  // Boy need knows that I have some abilities.
}

class LoliGril implement Girl {
  // I will implement Girl's abilities.
}

class Vixen implement Girl {
  // Vixen definitely is a girl, do not doubt it.
}

class Boy {
  protected $girl;

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

$loliGirl = new LoliGirl();
$vixen = new Vixen();

$boy = new Boy($loliGirl);
$boy = new Boy($vixen);

Boy 很高興,終於可以不用扒開自己就可以體驗不同的人生了。。。So Happy!
小結

    因爲大多數應用程序都是由兩個或者更多的類通過彼此合作來實現業務邏輯,這使得每個對象都需要獲取與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那麼將導致代碼高度耦合並且難以維護和調試。

所以纔有了依賴注入的概念,依賴注入解決了以下問題:

    依賴之間的解耦
    單元測試,方便 Mock

=。= 前面的依賴注入居然需要我們手動的去注入依賴,做爲程序員的我們怎麼可以容忍這種低效的注入方式,好吧,我們先來了解一下 IOC 的概念.
控制反轉 (Inversion Of Control, IOC)

    控制反轉 是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection, DI), 還有一種叫 "依賴查找"(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。

也就是說,我們需要一個調控系統,這個調控系統中我們存放一些對象的實體,或者對象的描述,在對象創建的時候將對象所依賴的對象的引用傳遞過去。
在 Laravel 中 Service Container 就是這個高效的調控系統,它是 laravel 的核心。
下面我們看一下 laravel 是如何實現自動依賴注入的。
laravel 中的依賴注入

現在我們看文檔給的例子應該就不難理解了:

<?php

namespace App\Jobs;

use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;

class PurchasePodcast implements SelfHandling
{
    /**
     * The mailer implementation.
     */
    protected $mailer;

    /**
     * Create a new instance.
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * Purchase a podcast.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

    In this example, the PurchasePodcast job needs to send e-mails when a podcast is purchased. So, we will inject a service that is able to send e-mails. Since the service is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the mailer when testing our application.

說到 laravel 中的依賴注入,我們不得不瞭解 laravel 的 Service Container
服務容器 (Service Container)

    The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.

從介紹不難看出服務容器就是控制反轉的容器,它就是前文說到的調度系統。實現依賴注入的方式可以是在構造函數中或者 setter 方法中。

如果我們仔細研究了 Service Container 我們就會發現 laravel 的服務容器中只存儲了對象的描述,而並不需要知道如何具體的去構造一個對象,因爲它會根據 php 的反射服務去自動解析具體化一個對象。
反射

    在計算機科學中,反射是指計算機在運行時(Run time)可以訪問、檢測和修改它本身狀態或行爲的一種能力。用來比喻說,那種程序能夠 “觀察” 並且修改自己的行爲。

    支持反射的語言提供了一些在低級語言中難以實現的運行時特性。這些特性包括

        作爲一個第一類對象發現並修改源代碼的結構(如代碼塊、類、方法、協議等)。
        將跟 class 或 function 匹配的轉換成 class 或 function 的調用或引用。
        在運行時像對待源代碼語句一樣計算字符串。
        創建一個新的語言字節碼解釋器來給編程結構一個新的意義或用途。

PHP 實現的反射可以在官網文檔中進行查看: 反射 API
Example

$reflector = new ReflectionClass('App\User');

if ($reflector->isInstantiable()) {
  $user = $refector->newInstance(); //in other case you can send any arguments
}

laravel 的服務容器的 build 方法中需要通過反射服務來解析依賴關係,比如說 construct 函數中需要傳遞的依賴參數有哪些?它就需要用到如下方法:

   $constructor = $reflector->getConstructor();

   // If there are no constructors, that means there are no dependencies then
   // we can just resolve the instances of the objects right away, without
   // resolving any other types or dependencies out of these containers.
   if (is_null($constructor)) {
       array_pop($this->buildStack);

       return new $concrete;
   }

   $dependencies = $constructor->getParameters();

現在我們應該對 laravel 如何實現依賴的自動注入有點想法了吧?來整理一下疑問:

    如何實現依賴的自動注入? (控制反轉,利用反射)
    依賴注入需要哪些東東? (整理依賴關係 [construct | setter],還要解析依賴傳遞引用)
    怎麼解析依賴?

你可能會問爲什麼要問怎麼解析依賴?解析依賴肯定是要用到反射的啦,反射,你知道類名不就可以直接解析了嗎?

其實。。。不是這樣的。。。(@ο@)

很多時候我們爲了提高代碼的擴展性和維護性,在編寫類時依賴的是接口或抽象類,而並不是一個具體的實現類。明白了嗎?依賴解析的時候如果只解析到接口或抽象類,然後利用反射,那麼這個依賴肯定是錯誤的。

那麼我們就需要在調度系統中注入相關依賴的映射關係,然後在需要的時候正確的解析關係。
比如說, 喂, 我需要一個 A, 你別給我 B 啊。

$container->bind('a', function () {
  return new B();  // just this for you
});

$a = $container->make('a');

總結

    依賴注入是控制反轉的一種實現,實現代碼解耦,便於單元測試。因爲它並不需要了解自身所依賴的類,而只需要知道所依賴的類實現了自身所需要的方法就可以了。你需要我,你卻不認識我/(ㄒoㄒ)/~~
    控制反轉提供一種調控系統,實現依賴解析的自動注入,一般配合容器提供依賴對象實例的引用。

推薦閱讀:

    Dependency Injection 和 Service Locator
    laravel 學習筆記 —— 神奇的服務容器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章