譯文首發於 什麼是依賴注入,轉載請註明出處。
本文是依賴注入(Depeendency Injection)系列教程的第一篇文章,本系列教程主要講解如何使用 PHP 實現一個輕量級服務容器,教程包括:php
這篇文章不會涉及有關「容器」相關知識的講解,而是經過一些實際的案例帶你去了解「依賴注入」這種設計模式試圖解決哪些問題,以及如何幫助咱們解決這些問題的。若是您已經掌握「依賴注入」相關概念,那麼能夠跳過這篇文章。html
「依賴注入」也許是我所知的最簡單的設計模式之一,有可能您已經在項目中使用過「依賴注入」,但同時它也是最難以講透徹的模式之一。究其緣由,大概是由於市面上已有講解「依賴注入」模式的文章,大多都在使用一些毫無實際意義的示例。在此以前,我已經嘗試使用 PHP 語言來設計一些「依賴注入」的示例。因爲 PHP 是一門 Web 開發而生,咱們仍是以一些簡單的 Web 實例做爲開場較爲合適。laravel
因爲 HTTP 協議是無狀態的協議,因此 Web 應用須要一種技術可以存儲用戶信息。經過使用 Cookie 或者 PHP 內置的「會話」機制可以輕鬆實現這樣的需求:web
<?php $_SESSION = 'fr';
上例能夠將用戶選擇的語言存儲到會話的 language 變量裏。以後,這位用戶發起的請求,均可以從 $_SESSION 數組中獲取 language 的值:數據庫
<?php $user_language = $_SESSION['language'];
因爲「依賴注入」基於面向對象設計,因此咱們須要將上面的功能封裝到 SessionStorage 類裏:設計模式
<?php class SessionStorage { public function __construct($cookieName = 'PHP_SESS_ID') { session_name($cookieName); session_start(); } public function set($key, $value) { $_SESSION[$key] = $value; } public function get($key) { return $_SESSION[$key]; } }
以及一個提供接口服務的類 User:數組
<?php class User { protected $storage; public function __construct() { $this->storage = new SessionStorage(); } public function setLanguage($language) { $this->storage->set('language', $language); } public function getLanguage() { return $this->storage->get('language'); } }
這個實例很是簡單,而且 User 類調用方法也十分簡單:緩存
<?php $user = new User(); $user->setLanguage('fr'); $user_language = $user->getLanguage();
一切都如此完美... 直到你須要擴展它。好比,你該如何修改 $this->storage 實例中的 cookie 名稱?通常有以下解決方案:性能優化
<?php class User { public function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } }
<?php class User { public function __construct() { $this->storage = new SessionStorage(STORAGE_SESSION_NAME); } } define('STORAGE_SESSION_NAME', 'SESSION_ID');
<?php class User { function __construct($sessionName) { $this->storage = new SessionStorage($sessionName); } // ... } $user = new User('SESSION_ID');
<?php class User { public function __construct($storageOptions) { $this->storage = new SessionStorage($storageOptions['session_name']); } // ... } $user = new User(array('session_name' => 'SESSION_ID'));
全部這些方案都不太好。在 User 類裏面硬編碼並無解決實際問題,後續你依舊沒法在不修改 User 類代碼的狀況下實現更改會話名稱的目的。使用一個常量也是一個壞主意,由於 User 類如今依賴於這個常量來設置。將會話名稱做爲參數傳遞或者做爲一組選項多是最好的解決方案,可是仍然很糟糕,由於這種方式將與 User 類無關的數據與 User 類耦合在一塊兒。cookie
另外,還有個問題也沒辦法輕鬆的解決:如何修改 SessionStorage 類?好比,須要使用「模擬」對象替換它用於測試。或者,須要替換會話存儲引擎到數據庫表或者內存。目前來看,咱們沒法在不修改 User 類的狀況下輕鬆實現。
「依賴注入」就是解決這種的問題,經過將 SessionStorage 對象以構造函數的參數傳給 User 實例,替換直接在 User 類中實例化的方式便可實現以上需求:
<?php class User { public function __construct($storage) { $this->storage = $storage; } // ... }
這就是「依賴注入」!此時,若是要使用 User 類,會稍微比以前要複雜一些:
<?php $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
這樣配置會話存儲對象和替換會話存儲實現類均可以輕鬆完成。得益於依賴的分離設計,在不改變 User 類的狀況下,一切皆有可能。
Pico Container website 是這樣描述依賴注入的:
「依賴注入」經過以構造函數參數,設值方法或屬性字段等方式將具體組件傳遞給依賴方(譯註:使用者)。與其餘設計模式同樣,依賴注入也有一些反模式。Pico Container website 描述了其中的一些反模式。
「依賴注入」並不侷限於經過構造函數注入這一種注入形式:
<?php class User { public function __construct($storage) { $this->storage = $storage; } // ... }
<?php class User { public function setSessionStorage($storage) { $this->storage = $storage; } }
<?php class User { public $sessionStorage; } $user->sessionStorage = $storage;
從個人經驗來看,經過構造函數注入適用於必要的依賴,如上例;設值注入適用於可選的依賴,如項目須要一個緩存功能的實現。
現在,不少 PHP 現代框架都依賴於「依賴注入」設計模式已達到高內聚低耦合的目標:
<?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 Moore 的分享。此外還有我去年有關 依賴注入的分享,這篇文章有更加細膩的依賴注入的解讀(譯註:可是很遺憾我一直打不開這個鏈接 :) )。
以上,就是今天所有內容。但願您對「依賴注入」有了更加深刻的瞭解。下一篇文章將聊聊「依賴注入容器」
資料
https://www.martinfowler.com/...
http://www.mendoweb.be/blog/d...
https://laravel-china.org/art...