鍍金池/ 教程/ PHP/ 依賴注入
依賴管理
安全
測試
使用模板
開發(fā)實(shí)踐
入門指南
服務(wù)器與部署
社區(qū)
語言亮點(diǎn)
錯誤與異常
虛擬化技術(shù)
資源
文檔撰寫
數(shù)據(jù)庫
依賴注入
緩存
代碼風(fēng)格指南

依賴注入

出自維基百科 Wikipedia:

依賴注入是一種允許我們從硬編碼的依賴中解耦出來,從而在運(yùn)行時(shí)或者編譯時(shí)能夠修改的軟件設(shè)計(jì)模式。

這句解釋讓依賴注入的概念聽起來比它實(shí)際要復(fù)雜很多。依賴注入通過構(gòu)造注入,函數(shù)調(diào)用或者屬性的設(shè)置來提供組件的依賴關(guān)系。就是這么簡單。

基本概念

我們可以用一個簡單的例子來說明依賴注入的概念

下面的代碼中有一個 Database 的類,它需要一個適配器來與數(shù)據(jù)庫交互。我們在構(gòu)造函數(shù)里實(shí)例化了適配器,從而產(chǎn)生了耦合。這會使測試變得很困難,而且 Database 類和適配器耦合的很緊密。

{% highlight php %}
<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}
{% endhighlight %}

這段代碼可以用依賴注入重構(gòu),從而解耦

{% highlight php %}
<?php
namespace Database;

class Database
{
    protected $adapter;

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

class MysqlAdapter {}
{% endhighlight %}

現(xiàn)在我們通過外界給予 Database 類的依賴,而不是讓它自己產(chǎn)生依賴的對象。我們甚至能用可以接受依賴對象參數(shù)的成員函數(shù)來設(shè)置,或者如果 $adapter 屬性本身是 public的,我們可以直接給它賦值。

復(fù)雜的問題

如果你曾經(jīng)了解過依賴注入,那么你可能見過 "控制反轉(zhuǎn)"(Inversion of Control) 或者 "依賴反轉(zhuǎn)準(zhǔn)則"(Dependency Inversion Principle)這種說法。這些是依賴注入能解決的更復(fù)雜的問題。

控制反轉(zhuǎn)

顧名思義,一個系統(tǒng)通過組織控制和對象的完全分離來實(shí)現(xiàn)"控制反轉(zhuǎn)"。對于依賴注入,這就意味著通過在系統(tǒng)的其他地方控制和實(shí)例化依賴對象,從而實(shí)現(xiàn)了解耦。

一些 PHP 框架很早以前就已經(jīng)實(shí)現(xiàn)控制反轉(zhuǎn)了,但是問題是,應(yīng)該反轉(zhuǎn)哪部分以及到什么程度?比如, MVC 框架通常會提供超類或者基本的控制器類以便其他控制器可以通過繼承來獲得相應(yīng)的依賴。這就是控制反轉(zhuǎn)的例子,但是這種方法是直接移除了依賴而不是減輕了依賴。

依賴注入允許我們通過按需注入的方式更加優(yōu)雅地解決這個問題,完全不需要任何耦合。

依賴反轉(zhuǎn)準(zhǔn)則

依賴反轉(zhuǎn)準(zhǔn)則是面向?qū)ο笤O(shè)計(jì)準(zhǔn)則 S.O.L.I.D 中的 "D" ,倡導(dǎo) "依賴于抽象而不是具體"。簡單來說就是依賴應(yīng)該是接口/約定或者抽象類,而不是具體的實(shí)現(xiàn)。我們能很容易重構(gòu)前面的例子,使之遵循這個準(zhǔn)則

{% highlight php %}
<?php
namespace Database;

class Database
{
    protected $adapter;

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

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}
{% endhighlight %}

現(xiàn)在 Database 類依賴于接口,相比依賴于具體實(shí)現(xiàn)有更多的優(yōu)勢。

假設(shè)你工作的團(tuán)隊(duì)中,一位同事負(fù)責(zé)設(shè)計(jì)適配器。在第一個例子中,我們需要等待適配器設(shè)計(jì)完之后才能單元測試。現(xiàn)在由于依賴是一個接口/約定,我們能輕松地模擬接口測試,因?yàn)槲覀冎劳聲诩s定實(shí)現(xiàn)那個適配器

這種方法的一個更大的好處是代碼擴(kuò)展性變得更高。如果一年之后我們決定要遷移到一種不同的數(shù)據(jù)庫,我們只需要寫一個實(shí)現(xiàn)相應(yīng)接口的適配器并且注入進(jìn)去,由于適配器遵循接口的約定,我們不需要額外的重構(gòu)。

容器

你應(yīng)該明白的第一件事是依賴注入容器和依賴注入不是相同的概念。容器是幫助我們更方便地實(shí)現(xiàn)依賴注入的工具,但是他們通常被誤用來實(shí)現(xiàn)反模式設(shè)計(jì) Service Location 。把一個依賴注入容器作為 Service Locator 注入進(jìn)類中隱式地建立了對于容器的依賴,而不是真正需要替換的依賴,而且還會讓你的代碼更不透明,最終變得更難測試。

大多數(shù)現(xiàn)代的框架都有自己的依賴注入容器,允許你通過配置將依賴綁定在一起。這實(shí)際上意味著你能寫出和框架層同樣干凈、解耦的應(yīng)用層代碼。

延伸閱讀