一 什麼是依賴注入?
此文是本人翻譯的來自國外某網站一篇文章 What is Dependency Injection?,第一次翻譯,各位見諒
這篇文章是一系列關於依賴注入和PHP輕量級容器實現文章中的一部分: Part 1: What is Dependency Injection? Part 2: Do you need a Dependency Injection Container? Part 3: Introduction to the Symfony Service Container Part 4: Symfony Service Container: Using a Builder to create Services
Part 5: Symfony Service Container: Using XML or YAML to describe Services Part 6: The Need for Speed
今天,我一開始不會講容器,我希望先通過一些具體的例項來介紹一下依賴注入的理念以及其所嘗試解決的問題和它能給開發者帶來的好處。如果你已經瞭解依賴注入,你可以跳過這篇文章去看下一篇。 依賴注入可能是我知道的最簡單的設計模式之一,很可能你已經使用過,但是同時也是最難解釋的,原因可能是大多數介紹依賴注入的文章用的例子都比較無聊。我想了一個比較適合PHP領域的例子,因為PHP主要用在web開發,所以讓我們來看一個簡單的web例項。 為瞭解決http協議無狀態的問題,web應用需要一種在web請求之間記錄使用者資訊的方法,簡單的通過cookie或者session都能解決:
$_SESSION['language'] = 'fr';
複製程式碼
上面的程式碼把使用者的語言存在了session變數裡面。這樣,對於同一個使用者的請求,其所使用的語言就會被儲存在$_SESSION陣列裡面,我們可以這樣獲取:
$user_language = $_SESSION['language'];
複製程式碼
由於依賴注入只在面向物件的世界裡有意義,我們假裝我們有一個叫SessionStorage的類封裝了處理session的方法:
class SessionStorage
{
function __construct($cookieName = 'PHP_SESS_ID')
{
session_name($cookieName );
session_start();
}
function set($key,$value)
{
$_SESSION[$key] = $value;
}
function get($key)
{
return $_SESSION[$key];
}
// ...
}
複製程式碼
...和一個提供高階介面的易用的User類
class User
{
protected $storage;
function __construct()
{
$this->storage = new SessionStorage();
}
function setLanguage($language)
{
$this->storage->set('language',$language);
}
function getLanguage()
{
return $this->storage->get('language');
}
// ...
}
複製程式碼
這些類足夠簡單,使用User類也非常容易:
$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();
複製程式碼
到目前為止,一切都很好...除非你想要更多的靈活性。萬一你想要改變session裡面的cookie名字呢?你可能會使用下面這些方法:
- 在SessionStorage構造器裡面硬編碼名字
class User
{
function __construct()
{
$this->storage = new SessionStorage('SESSION_ID');
}
// ...
}
複製程式碼
- 在User類外面定義一個常量
define('STORAGE_SESSION_NAME','SESSION_ID');
class User
{
function __construct()
{
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);
}
// ...
}
複製程式碼
- 在User類構造器裡面傳遞一個名字作為引數
class User
{
function __construct($sessionName)
{
$this->storage = new SessionStorage($sessionName);
}
// ...
}
$user = new User('SESSION_ID');
複製程式碼
- 在User類構造器裡面傳遞一個陣列選項
class User
{
function __construct($storageOptions)
{
$this->storage = new SessionStorage($storageOptions['session_name']);
}
// ...
}
$user = new User(array('session_name' => 'SESSION_ID'));
複製程式碼
以上的所有選擇都很爛,硬編碼名字沒有真正解決問題因為你以後可能隨時會改變注意,你還得更改User類。使用常量也是一個壞注意,因為你又依賴了一個常量。通過傳遞一個陣列引數可能是一個好的解決方案,但是依然不太好,它把User構造器和一個和它本身不相關的東西耦合了。 而且還有一個問題沒法容易搞定:我怎麼換掉SessionStorage類?比方說,用一個mock物件去測試,或者你想把session儲存在資料庫或記憶體裡面。在目前的程式碼裡面除非你更改User類,否則無法實現。
###依賴注入 不要在User類裡面建立SessionStorage物件,我們在類外面建立SessionStorage物件,然後通過建構函式把其作為引數傳進來:
class User
{
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
複製程式碼
這就是依賴注入,就是這些!
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
複製程式碼
現在,配置一個session儲存物件非常簡單了,替換它也很容易,不用改變User類也可以實現其他功能。 Pico Container website 這樣形容依賴注入:“依賴注入就是通過構造器、方法、屬性獲取所需要的元素” 依賴注入不僅僅侷限於此:
- 構造器注入:
class User
{
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
複製程式碼
- Setter注入:
class User
{
function setSessionStorage($storage)
{
$this->storage = $storage;
}
// ...
}
複製程式碼
- 屬性注入:
class User
{
public $sessionStorage;
}
$user->sessionStorage = $storage;
複製程式碼
一般來說,構造器注入最適合必要依賴,就像例子裡面那樣,Setter注入比較適合可選依賴,比如說快取物件。當今,很多現代PHP框架大量使用依賴注入提供一系列既解耦又有凝聚力的元件:
// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage(array('database' => 'session','db_table' => 'session'));
$user = new sfUser($dispatcher,$storage,array('default_culture' => 'en'));
// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com',array(
'auth' => 'login','username' => 'foo','password' => 'bar','ssl' => 'ssl','port' => 465,));
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);
複製程式碼
如果你想了解更多關於依賴注入的東西,我強烈建議你讀一讀Martin Fowler introduction 或者 Jeff More presentation。你也可以看看我去年關於依賴注入的演講,這裡講了更多細節
好了,就說這麼多了,我希望你現在對依賴注入有更好的理解,本系列的下一章我會講關於依賴注入容器