本文是PHP依賴注入容器的實作這個系列的第一章。
今天,先不談容器(container),首先用一些具體的例子來介紹依賴注入的概念,證明依賴注入這種模式可以解決哪些問題,同時能給開發人員帶來哪些好處。
如果你已經知道了依賴注入的概念,你可以跳過這篇文章。
依賴注入可能是我所知道的最簡單設計模式之一,很多情況下可能你無意識中已經使用了依賴注入。不過它也是最難解釋的一個。我認為有一部分原因是由於大多數介紹依賴注入的例子缺乏實際意義,所以讓人難理解。因為PHP主要用於Web開發,那就先來看一個簡單的web開發實例。
HTTP本身是一個無狀態的連接協議,為了支援客戶在發起WEB請求時應用程式能儲存使用者訊息,我們需要透過一種技術來實現儲存狀態互動。理所當然最簡單的是使用cookie,更好的方式是PHP內建的Session機制。
$_SESSION['language'] = 'fr';
上面程式碼將使用者語言儲存在了名為language的Session變數中,因此在該使用者隨後的請求中,可以透過全域陣列$_SESSION來取得language:
$user_language = $_SESSION['language'];
依賴注入主要用於物件導向開發,現在讓我們假設我們有一個SessionStorage類,該類封裝了PHP 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_id的COOKIE鍵值,以下有一些可供選擇的方法:
User類別中建立SessionStorage實例時,在SessionStorage在建構方法中使用字串'SESSION_ID'硬編碼:
class User { function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } // ... }
User類別外部設定一個常數(名為STORAGE_SESSION_NAME)
class User { function __construct() { $this->storage = new SessionStorage(STORAGE_SESSION_NAME); } // ... } define('STORAGE_SESSION_NAME', 'SESSION_ID');
透過User類別建構子中的參數傳遞Session name
class User { function __construct($sessionName) { $this->storage = new SessionStorage($sessionName); } // ... } $user = new User('SESSION_ID');
還是透過UserSession name,這次參數中的參數陣列的方式
class User { function __construct($storageOptions) { $this->storage = new SessionStorage($storageOptions['session_name']); } // ... } $user = new User(array('session_name' => 'SESSION_ID'));
上面的方式都很糟糕。
在user類別中硬編碼設定session name的做法沒有真正解決問題,如果以後你還需要更改保存session_id的COOKIE鍵值,你不得不再一次修改user類別(User類別不應該關心COOKIE鍵值)。
使用常數的方式同樣很糟,造成User類別依賴一個常數設定。
透過User類別建構函數的參數或陣列傳遞session name相對來說好一些,不過也不完美,這樣做幹擾了User類別建構函數的參數,因為如何儲存Session並不是User類別需要關心的,User類不應該和它們扯上關聯。
另外,還有一個問題不太好解決:我們如何改變SessionStorage類別。這種應用場景很多,例如你要用一個Session模擬類別來做測試,或是你要將Session儲存在資料庫或記憶體中。目前這種實作方式,在不改變User類別的情況下,很難做到這一點。
現在,讓我們來使用依賴注入。回想一下,之前我們是在User類別內部建立SessionStorage物件的,現在我們修改一下,讓SessionStorage物件透過User類別的建構子傳遞進去。
class User { function __construct($storage) { $this->storage = $storage; } // ... }
這就是依賴注入最經典的案例,沒有之一。現在使用User類別有一些小小的改變,首先你需要建立SessionStorage物件。
$storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
現在,配置session存儲對像很簡單了,同樣如果改變session存儲對像也很簡單,所有這一切並不需要去更新User類,降低了業務類之間的耦合。
Pico Container 的網站上是這樣描述依賴注入:
依賴注入是透過類別的建構子、方法、或直接寫入的方式,將所依賴的元件傳遞給類別的方式。
所以依賴注入不限於透過構造函數注入。以下來看看幾種注入方式:
建構子注入
class User { function __construct($storage) { $this->storage = $storage; } // ... }
setter方法注入
class User { function setSessionStorage($storage) { $this->storage = $storage; } // ... }
屬性直接注入
class User { public $sessionStorage; } $user->sessionStorage = $storage;
现在,大多数流行的PHP框架都采用了依赖注入的模式实现业务组件间的高内聚低耦合。
// symfony: 构造函数注入的例子 $dispatcher = new sfEventDispatcher(); $storage = new sfMySQLSessionStorage(array('database' => 'session', 'db_table' => 'session')); $user = new sfUser($dispatcher, $storage, array('default_culture' => 'en')); // Zend Framework: setter方式注入的例子 $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);
以上就是理解PHP依赖注入容器系列(一) 什么是的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!