# Dependency Injection Explained
接下來的例子有些長,但解釋了為什么我們使用依賴注入與服務定位器. 首先,假設我們正在開發一個組件,叫SomeComponent,它執行的內容現在還不重要。 我們的組件需要依賴數據庫的連接。
在下面第一個例子中,數據庫的連接是在組件內部建立的。這種方法是不實用的;事實上這樣做的話,我們不能改變創建數據庫連接的參數或者選擇不同的數據庫系統,因為連接是當組件被創建時建立的。
~~~
<?php
class SomeComponent
{
/**
* 連接數據庫的實例是被寫死在組件的內部
* 因此,我們很難從外部替換或者改變它的行為
*/
public function someDbTask()
{
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
~~~
為了解決這樣的情況,我們建立一個setter,在使用前注入獨立外部依賴。現在,看起來似乎是一個不錯的解決辦法:
~~~
<?php
class SomeComponent
{
protected $_connection;
/**
* 設置外部傳入的數據庫的連接實例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
// 建立數據庫連接實例
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
// 向組件注入數據連接實例
$some->setConnection($connection);
$some->someDbTask();
~~~
想一下,假設我們使用這個組件在應用內的好幾個地方都用到,然而我們在注入連接實例時還需要建立好幾次數據的連接實例。 如果我們可以獲取到數據庫的連接實例而不用每次都要創建新的連接實例,使用某種全局注冊表可以解決這樣的問題:
~~~
<?php
class Registry
{
/**
* 返回數據庫連接實例
*/
public static function getConnection()
{
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
}
class SomeComponent
{
protected $_connection;
/**
* 設置外部傳入的數據庫的連接實例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
// 把注冊表中的連接實例傳遞給組件
$some->setConnection(Registry::getConnection());
$some->someDbTask();
~~~
現在,讓我們設想一下,我們必須實現2個方法,第一個方法是總是創建一個新的連接,第二方法是總是使用一個共享連接:
~~~
<?php
class Registry
{
protected static $_connection;
/**
* 建立一個新的連接實例
*/
protected static function _createConnection()
{
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
/**
* 只建立一個連接實例,后面的請求只返回該連接實例
*/
public static function getSharedConnection()
{
if (self::$_connection === null) {
self::$_connection = self::_createConnection();
}
return self::$_connection;
}
/**
* 總是返回一個新的連接實例
*/
public static function getNewConnection()
{
return self::_createConnection();
}
}
class SomeComponent
{
protected $_connection;
/**
* 設置外部傳入的數據庫的連接實例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
/**
* 這個方法總是需要共享連接實例
*/
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
/**
* 這個方法總是需要新的連接實例
*/
public function someOtherDbTask($connection)
{
}
}
$some = new SomeComponent();
// 注入共享連接實例
$some->setConnection(
Registry::getSharedConnection()
);
$some->someDbTask();
// 這里我們總是傳遞一個新的連接實例
$some->someOtherDbTask(
Registry::getNewConnection()
);
~~~
到目前為止,我們已經看到依賴注入怎么解決我們的問題了。把依賴作為參數來傳遞,而不是建立在內部建立它們,這使我們的應用更加容易維護和更加解耦。不管怎么樣,長期來說,這種形式的依賴注入有一些缺點。
例如,如果這個組件有很多依賴, 我們需要創建多個參數的setter方法??來傳遞依賴關系,或者建立一個多個參數的構造函數來傳遞它們,另外在使用組件前還要每次都創建依賴,這讓我們的代碼像這樣不易維護:
~~~
<?php
// 創建依賴實例或從注冊表中查找
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
// 把實例作為參數傳遞給構造函數
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... 或者使用setter
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
~~~
假設我們必須在應用的不同地方使用和創建這些對象。如果當你永遠不需要任何依賴實例時,你需要去刪掉構造函數的參數,或者去刪掉注入的setter。為了解決這樣的問題,我們再次回到全局注冊表創建組件。不管怎么樣,在創建對象之前,它增加了一個新的抽象層:
~~~
<?php
class SomeComponent
{
// ...
/**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
return new self($connection, $session, $fileSystem, $filter, $selector);
}
}
~~~
瞬間,我們又回到剛剛開始的問題了,我們再次創建依賴實例在組件內部!我們可以繼續前進,找出一個每次能奏效的方法去解決這個問題。但似乎一次又一次,我們又回到了不實用的例子中。
一個實用和優雅的解決方法,是為依賴實例提供一個容器。這個容器擔任全局的注冊表,就像我們剛才看到的那樣。使用依賴實例的容器作為一個橋梁來獲取依賴實例,使我們能夠降低我們的組件的復雜性:
~~~
<?php
use Phalcon\Di;
use Phalcon\DiInterface;
class SomeComponent
{
protected $_di;
public function __construct(DiInterface $di)
{
$this->_di = $di;
}
public function someDbTask()
{
// 獲得數據庫連接實例
// 總是返回一個新的連接
$connection = $this->_di->get("db");
}
public function someOtherDbTask()
{
// 獲得共享連接實例
// 每次請求都返回相同的連接實例
$connection = $this->_di->getShared("db");
// 這個方法也需要一個輸入過濾的依賴服務
$filter = $this->_di->get("filter");
}
}
$di = new Di();
// 在容器中注冊一個db服務
$di->set(
"db",
function () {
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
);
// 在容器中注冊一個filter服務
$di->set(
"filter",
function () {
return new Filter();
}
);
// 在容器中注冊一個session服務
$di->set(
"session",
function () {
return new Session();
}
);
// 把傳遞服務的容器作為唯一參數傳遞給組件
$some = new SomeComponent($di);
$some->someDbTask();
~~~
這個組件現在可以很簡單的獲取到它所需要的服務,服務采用延遲加載的方式,只有在需要使用的時候才初始化,這也節省了服務器資源。這個組件現在是高度解耦。例如,我們可以替換掉創建連接的方式,它們的行為或它們的任何其他方面,也不會影響該組件。
- 簡介
- 安裝
- 安裝(installlation)
- XAMPP下的安裝
- WAMP下安裝
- Nginx安裝說明
- Apache安裝說明
- Cherokee 安裝說明
- 使用 PHP 內置 web 服務器
- Phalcon 開發工具
- Linux 系統下使用 Phalcon 開發工具
- Mac OS X 系統下使用 Phalcon 開發工具
- Windows 系統下使用 Phalcon 開發工具
- 教程
- 教程 1:讓我們通過例子來學習
- 教程 2:INVO簡介
- 教程 3: 保護INVO
- 教程4: 使用CRUD
- 教程5: 定制INVO
- 教程 6: V?kuró
- 教程 7:創建簡單的 REST API
- 組件
- 依賴注入與服務定位器
- MVC架構
- 使用控制器
- 使用模型
- 模型關系
- 事件與事件管理器
- Behaviors
- 模型元數據
- 事務管理
- 驗證數據完整性
- Workingwith Models
- Phalcon查詢語言
- 緩存對象關系映射
- 對象文檔映射 ODM
- 使用視圖
- 視圖助手
- 資源文件管理
- Volt 模版引擎
- MVC 應用
- 路由
- 調度控制器
- Micro Applications
- 使用命名空間
- 事件管理器
- Request Environmen
- 返回響應
- Cookie 管理
- 生成 URL 和 路徑
- 閃存消息
- 使用 Session 存儲數據
- 過濾與清理
- 上下文編碼
- 驗證Validation
- 表單_Forms
- 讀取配置
- 分頁 Pagination
- 使用緩存提高性能
- 安全
- 加密與解密 Encryption/Decryption
- 訪問控制列表
- 多語言支持
- 類加載器 Class Autoloader
- 日志記錄_Logging
- 注釋解析器 Annotations Parser
- 命令行應用 Command Line Applications
- Images
- 隊列 Queueing
- 數據庫抽象層
- 國際化
- 數據庫遷移
- 調試應用程序
- 單元測試
- 進階技巧與延伸閱讀
- 提高性能:下一步該做什么?
- Dependency Injection Explained
- Understanding How Phalcon Applications Work
- Api
- Abstract class Phalcon\Acl