此文是本人翻譯的來自國(guó)外某網(wǎng)站一篇文章 What is Dependency Injection?,第一次翻譯蒸绩,各位見諒
這篇文章是一系列關(guān)于依賴注入和PHP輕量級(jí)容器實(shí)現(xiàn)文章中的一部分:
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
今天,我一開始不會(huì)講容器铃肯,我希望先通過一些具體的實(shí)例來介紹一下依賴注入的理念以及其所嘗試解決的問題和它能給開發(fā)者帶來的好處患亿。如果你已經(jīng)了解依賴注入,你可以跳過這篇文章去看下一篇。
依賴注入可能是我知道的最簡(jiǎn)單的設(shè)計(jì)模式之一步藕,很可能你已經(jīng)使用過惦界,但是同時(shí)也是最難解釋的,原因可能是大多數(shù)介紹依賴注入的文章用的例子都比較無聊咙冗。我想了一個(gè)比較適合PHP領(lǐng)域的例子沾歪,因?yàn)镻HP主要用在web開發(fā),所以讓我們來看一個(gè)簡(jiǎn)單的web實(shí)例雾消。
為了解決http協(xié)議無狀態(tài)的問題,web應(yīng)用需要一種在web請(qǐng)求之間記錄用戶信息的方法狂窑,簡(jiǎn)單的通過cookie或者session都能解決:
$_SESSION['language'] = 'fr';
上面的代碼把用戶的語言存在了session變量里面桑腮。這樣,對(duì)于同一個(gè)用戶的請(qǐng)求旨巷,其所使用的語言就會(huì)被存儲(chǔ)在$_SESSION數(shù)組里面采呐,我們可以這樣獲雀槠铩:
$user_language = $_SESSION['language'];
由于依賴注入只在面向?qū)ο蟮氖澜缋镉幸饬x,我們假裝我們有一個(gè)叫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];
}
// ...
}
...和一個(gè)提供高級(jí)接口的易用的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');
}
// ...
}
這些類足夠簡(jiǎn)單煤率,使用User類也非常容易:
$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();
到目前為止蝶糯,一切都很好...除非你想要更多的靈活性昼捍。萬一你想要改變session里面的cookie名字呢肢扯?你可能會(huì)使用下面這些方法:
- 在SessionStorage構(gòu)造器里面硬編碼名字
class User
{
function __construct()
{
$this->storage = new SessionStorage('SESSION_ID');
}
// ...
}
- 在User類外面定義一個(gè)常量
define('STORAGE_SESSION_NAME', 'SESSION_ID');
class User
{
function __construct()
{
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);
}
// ...
}
- 在User類構(gòu)造器里面?zhèn)鬟f一個(gè)名字作為參數(shù)
class User
{
function __construct($sessionName)
{
$this->storage = new SessionStorage($sessionName);
}
// ...
}
$user = new User('SESSION_ID');
- 在User類構(gòu)造器里面?zhèn)鬟f一個(gè)數(shù)組選項(xiàng)
class User
{
function __construct($storageOptions)
{
$this->storage = new SessionStorage($storageOptions['session_name']);
}
// ...
}
$user = new User(array('session_name' => 'SESSION_ID'));
以上的所有選擇都很爛乍钻,硬編碼名字沒有真正解決問題因?yàn)槟阋院罂赡茈S時(shí)會(huì)改變注意,你還得更改User類多糠。使用常量也是一個(gè)壞注意欢摄,因?yàn)槟阌忠蕾嚵艘粋€(gè)常量。通過傳遞一個(gè)數(shù)組參數(shù)可能是一個(gè)好的解決方案析蝴,但是依然不太好闷畸,它把User構(gòu)造器和一個(gè)和它本身不相關(guān)的東西耦合了吞滞。
而且還有一個(gè)問題沒法容易搞定:我怎么換掉SessionStorage類?比方說殿漠,用一個(gè)mock對(duì)象去測(cè)試佩捞,或者你想把session保存在數(shù)據(jù)庫(kù)或內(nèi)存里面一忱。在目前的代碼里面除非你更改User類,否則無法實(shí)現(xiàn)票渠。
依賴注入
不要在User類里面創(chuàng)建SessionStorage對(duì)象芬迄,我們?cè)陬愅饷鎰?chuàng)建SessionStorage對(duì)象,然后通過構(gòu)造函數(shù)把其作為參數(shù)傳進(jìn)來:
class User
{
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
這就是依賴注入,就是這些择诈!
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
現(xiàn)在出皇,配置一個(gè)session存儲(chǔ)對(duì)象非常簡(jiǎn)單了郊艘,替換它也很容易,不用改變User類也可以實(shí)現(xiàn)其他功能畏浆。
Pico Container website 這樣形容依賴注入:“依賴注入就是通過構(gòu)造器刻获、方法瞎嬉、屬性獲取所需要的元素”
依賴注入不僅僅局限于此:
- 構(gòu)造器注入:
class User
{
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
- Setter注入:
class User
{
function setSessionStorage($storage)
{
$this->storage = $storage;
}
// ...
}
- 屬性注入:
class User
{
public $sessionStorage;
}
$user->sessionStorage = $storage;
一般來說氧枣,構(gòu)造器注入最適合必要依賴,就像例子里面那樣扎谎,Setter注入比較適合可選依賴烧董,比如說緩存對(duì)象。當(dāng)今预吆,很多現(xiàn)代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);
如果你想了解更多關(guān)于依賴注入的東西啡浊,我強(qiáng)烈建議你讀一讀Martin Fowler introduction 或者 Jeff More presentation胶背。你也可以看看我去年關(guān)于依賴注入的演講,這里講了更多細(xì)節(jié)
好了钳吟,就說這么多了,我希望你現(xiàn)在對(duì)依賴注入有更好的理解坝茎,本系列的下一章我會(huì)講關(guān)于依賴注入容器