如果說學院派的 Java 程式員骨子裡都浸淫着學究範兒的話,那麼遊擊隊出身的 PHP 程式員則從頭到腳洋溢着鄉土氣息。通常他們不太在意理論,一切以實作為先,雖然這樣的做法在項目早期能獲得不錯的收益,但是随着項目的推進,複雜度的提升,缺乏理論基礎的弊端終将顯現。好在 PHP 社群沒有裹足不前,比如說十幾年前 Java 社群中流行的 IoC 概念,最近一兩年終于被 PHP 社群所接納。
說起 IoC,其實是 Inversion of Control 的縮寫,翻譯成中文叫控制反轉,不得不說這個名字起得讓人丈二和尚摸不着頭腦,實際上簡而言之它的意思是說對象之間難免會有各種各樣的依賴關系,如果我們的代碼直接依賴于具體的實作,那麼就是一種強耦合,進而降低了系統的靈活性,為了解耦,我們的代碼應該依賴接口,至于具體的實作,則通過第三方注入進去,這裡的第三方通常就是我們常說的容器。因為在這個過程中,具體實作的控制權從我們的代碼轉移到了容器,是以稱之為控制反轉。
如果你看過 Martin Fowler 關于 IoC 的介紹,那麼你就會知道 IoC 是一個寬泛的概念,具體點兒說有兩種不同的實作方式,分别是:Dependency Injection 和 Service Locator。現在很多 PHP 架構都實作了容器,比如 Phalcon (1),Yii (1)(2),Laravel (1)(2) 等。
至于 Dependency Injection 和 Service Locator 的差別,與其說一套雲山霧繞的概念,不能給出幾個鮮活的例子來得自然,為了偷懶,我直接套用 TheKeyboard 的文章:
如果沒有容器,那麼 Dependency Injection 看起來就像:
<?php
class Foo
{
protected $_bar;
protected $_baz;
public function __construct(Bar $bar, Baz $baz) {
$this->_bar = $bar;
$this->_baz = $baz;
}
}
// In our test, using PHPUnit's built-in mock support
$bar = $this->getMock('Bar');
$baz = $this->getMock('Baz');
$testFoo = new Foo($bar, $baz);
?>
複制
如果有容器,那麼 Dependency Injection 看起來就像:
<?php
// In our test, using PHPUnit's built-in mock support
$container = $this->getMock('Container');
$container['bar'] = $this->getMock('Bar');
$container['baz'] = $this->getMock('Baz');
$testFoo = new Foo($container['bar'], $container['baz']);
?>
複制
通過引入容器,我們可以把所有的依賴都集中管理,這樣有很多好處,比如說我們可以很友善的替換某種依賴的實作方式,進而提升系統的靈活性。
看看下面這個實作怎麼樣?是不是 Dependency Injection?
<?php
class Foo
{
protected $_bar;
protected $_baz;
public function __construct(Container $container) {
$this->_bar = $container['bar'];
$this->_baz = $container['baz'];
}
}
// In our test, using PHPUnit's built-in mock support
$container = $this->getMock('Container');
$container['bar'] = $this->getMock('Bar');
$container['baz'] = $this->getMock('Bar');
$testFoo = new Foo($container);
?>
複制
雖然從表面上看它也使用了容器,并不依賴具體的實作,但你如果仔細看就會發現,它依賴了容器本身,實際上這不是 Dependency Injection,而是 Service Locator。
于是乎判斷 Dependency Injection 和 Service Locator 差別的關鍵是在哪使用容器:
- 如果在非工廠對象的外面使用容器,那麼就屬于 Dependency Injection。
- 如果在非工廠對象的内部使用容器,那麼就屬于 Service Locator。
之是以排除工廠對象是因為它是一種特殊的對象,它關注的是建立對象,而不是操作對象,具體的解釋可以參考 Paul M. Jones 在一系列文章中的解釋。
說到這裡,我想順帶提一下 Laravel 的 Facade 概念,它是一種 Service Locator 的文法糖,原理可以參考:How Laravel Facades Work and How to Use Them Elsewhere。很多人建議 Stop Using Facades,Laravel 作者也給出了回應。
BTW:Laravel 中的 Facade 實際有誤導之嫌,詳見:Let’s Talk About Facades。
說了這麼多,我們應該如何取舍 Dependency Injection 和 Service Locator 呢?實際上它們各有各的優缺點,比如說 Dependency Injection 解耦更徹底,而 Service Locator 使用更直接。如果是一些可複用性強的對象,如 Model,那麼它的依賴最好使用 Dependency Injection 來擷取;如果是一些可複用性弱的對象,如 Controller,那麼它的依賴并不一定要強解耦,使用 Service Locator 來擷取也不錯,這樣也更簡單直接。