在上一篇中我們透過一個具體的Web案例來說明依賴注入,今天我們將談到依賴注入的容器(Container),首先讓我們從一個重要的聲明開始:
大多數時侯,你在使用依賴注入方式解耦元件時,並不需要用到容器。
但是如果你要管理很多不同的對象,並且要處理複雜繁多的對象間的依賴關係時,容器就變得很有用了。
還記得第一篇的範例嗎,在建立User物件前要先建立一個SessionStorage物件。這沒什麼大不了,但我還是想說,這是因為你在創建你所需要的對象之前,你清楚的知道它所依賴的對象了。如果物件很多,依賴關係很複雜(假設SessionStorage類別依賴cache類,cache類別依賴file類別和inputFilter類別,file類別依賴stdio類別),那就呵呵了。 。 。 。
$storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
在後面的文章中我們將介紹Symfony 2中實現容器的方式。不過現在為了能簡單、清楚的說明容器,我們先無視Symfony。以下將採用 Zend Framework中的一個範例來說明:
Zend Framework 的Zend_Mail類別簡化了email管理,它預設使用PHP的mail()函數來傳送郵件,但是彈性欠佳。不過謝天謝地,可以透過提供transport類別來輕鬆改變這些行為。
下面的程式碼展示如何建立Zend_Mail類別並使用Gmail帳號來傳送郵件
$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);
依賴注入容器(Dependency Injection Container)是一個大類,它能實例化並配置他所管理的各種元件和類別。為了能做到這些,它必須知道這些類別的建構方法的參數,依賴關係。
下面是一個硬編碼的容器,還是實現之前所提到的獲取Zend_Mail對象的工作:
class Container { public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } } //容器的使用也很简单 $container = new Container(); $mailer = $container->getMailer();
在使用容器的時侯,如果需要獲得一個Zend_Mail對象,並不需要知道創建它的細節,因為所有創建對象實例的細節都內建到容器中實現了。 Zend_Mail對Mail_Transport類別的依賴也能透過容器自動注入Zend_Mail物件中。
取得依賴物件主要是getMailTransport()實作的,容器的強大之處就是靠這個簡單的get呼叫實現的。
但是聰明的你一定發現了問題,容器裡面有硬編碼(如,發送郵件的帳號密碼資訊等)。所以我們要更進一步,為容器加入參數,讓容器變得更有用。
class Container { protected $parameters = array(); public function __construct(array $parameters = array()) { $this->parameters = $parameters; } public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => $this->parameters['mailer.username'], 'password' => $this->parameters['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
現在透過容器建構函數的參數可以很容易切換發送郵件的帳號和密碼
$container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', )); $mailer = $container->getMailer();
如果覺得Zend_Mail類不能滿足當前需要(比如測試時,需要做一些日誌),想輕易切換郵件發送類,同樣可以透過容器建構函數的參數傳遞類別名稱
class Container { // ... public function getMailer() { $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } } $container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', 'mailer.class' => 'MyTest_Mail', )); $mailer = $container->getMailer();
最後,考慮到客戶取得mailer物件時並不需要每次都重新實例化一下(佔用開銷),容器應該每次提供相同的物件實例。
因此,程式使用了protected的靜態陣列$shared,用來儲存第一次實例化的物件,以後使用者取得mailer時都會傳回第一次實例化的物件
class Container { static protected $shared = array(); // ... public function getMailer() { if (isset(self::$shared['mailer'])) { return self::$shared['mailer']; } $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return self::$shared['mailer'] = $mailer; } }
容器封裝了這些基本功能,容器需要管理的內容包括物件的實例化和配置。這些物件本身並不知道自己被容器管理,也可以無視容器的存在。這也就是為什麼容器可以管理任何PHP類別。如果物件本身在對依賴關係的處理使用了依賴注入這種方式就更好了,當然這不是必須的。
不過手工創建和維護容器很快會變成噩夢。後面的文章講會講述Symfony 2是怎麼實現容器的。
以上就是理解PHP依賴注入容器系列(二) 你需要的內容,更多相關內容請關注PHP中文網(www.php.cn)!