依賴注入
出自 維基百科:
依賴注入是一個讓我們可以移除寫死的相依關係, 並在執行期間或編譯期間改變相依關係的軟體設計模式
這句話讓這個概念聽起來比實際上來的要複雜許多。 依賴注入是透過建構式注入、方法呼叫或設定屬性的其中任何一個來提供元件跟它的相依關係。 就是這麼簡單。
基本概念
我們可以用一個簡單而樸實的例子來說明這個概念。
這裡我們有一個 Database
類別它需要個配接器來跟資料庫溝通。
我們在建構式實例化配接器並建立寫死的相依關係。
這使得測試困難並意味 Database
類別跟配接器有非常緊密地耦合。
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct()
{
$this->adapter = new MySqlAdapter;
}
}
class MysqlAdapter {}
這段程式碼可以用依賴注入重構並因此鬆開了相依關係。
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct(MySqlAdapter $adapter)
{
$this->adapter = $adapter;
}
}
class MysqlAdapter {}
現在我們給 Database
類別它的相依關係而不是它自己建立相依關係。 我們可以建立
接受一個相依關係參數的方法並用這個方法設定它,或者 $adapter
屬性是 public
我們可以直接設定它。
複雜的問題
如果你曾經讀過依賴注入,那你可能已經看過 "控制反轉" 或 "依賴反轉原則" 這詞彙。 這些都是依賴注入解決的複雜問題。
控制反轉
控制反轉就像字面上說的,藉由維持組織的控制從我們的物件完全地分離來「反向控制」一個系統。 從依賴注入的觀點看,這意味藉由在系統別處控制和實例化它們,鬆開相依關係。
多年來,PHP 框架已經實現了控制反轉,然而,問題變成,你要反向控制哪個部分、在哪控制? 舉個例子,MVC 框架通常會提供一個超級物件或基底控制器,其他控制器必須繼承它以取用它的相依關係。 這 就是 控制反轉,然而,這個方法簡單地搬移它們,而不是鬆開相依關係。
依賴注入讓我們藉由在我們需要的時候只注入我們需要的相依關係,更優雅地解決這個問題, 完全不需要任何寫死的相依關係。
依賴反轉原則
依賴反轉原則是物件導向設計原則的 S.O.L.I.D 集合中的「D」,它陳述應該 "依賴於抽象。 而不要依賴於實例"。 簡單的說,這意味著我們的相依關係應該是介面/契約或抽象類別而不是具體的實作。 我們可以簡單地重構上面的例子來遵循這個原則。
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
}
interface AdapterInterface {}
class MysqlAdapter implements AdapterInterface {}
Database
類別現在依賴於介面而不是實例有幾個好處。
試想,你在一個團隊中工作並且配接器是由同事動工。 在我們的第一個例子中,在可以適當地在單元測試中模擬它之前,我們必須等待同事完成配接器。 現在,相依關係是個 介面/契約,因為我們知道同事將會基於那個契約來做配接器,我們可以快樂地模擬那個介面。
這個方法有一個更大的好處是我們的程式碼現在變成更可擴展的。 如果一年下來我們終究決定想要遷移到一個不同種類的資料庫,我們可以寫一個實作原本介面的配接器並注入它來取代, 當我們可以確保配接器藉由介面遵守契約規定,就不再需要一直重構了。
容器
第一件你應該了解有關依賴注入容器的事就是它跟依賴注入是不同的東西。 容器是個便利的工具能幫助我們實作依賴注入,然而,它們經常被濫用來實作一個反模式:服務定位。 注入一個依賴注入容器作為服務定位器到你的類別中,可以說是建立了比你替換的相依關係更死的相依關係在容器中。 這也讓你的程式碼更不透明並最後更難測試。
大部份現代框架都有它們自己的依賴注入容器,讓你透過設定去繫結你的相依關係。 這意味著實際上你可以寫乾淨且去耦合的程式碼,如同應用程式使用的框架。