php - Dependency Injection依賴注入 和 自動加載 各自的優缺點
ioc/di和自動加載時兩回事。 ioc/di 讓代碼由建立對象改成注入對象,是一種編程思想,而自動加載,只是省略reqire文件而已。 ioc/di我認爲有如下好處: 第一,把對象的建立從業務代碼裏抽出來。 第二,統一一個對象的建立方式,避免處處使用本身的方式建立對象。 第三,使用了建造者模式,將某些對象複雜的建造過程封裝起來。 ioc/di我認爲有如下壞處: 第一:濫用使得代碼沒法被跟蹤到,我一個類的一個方法,在項目哪一個地方用到了,ioc/di提供了一種途徑,也是如今不少框架使用的途徑,讓這種代碼已經沒法跟蹤到了。 第二:硬編碼,我傳一個字符串獲取一個對象,若是之後這種對應關係不在或發生變化了怎麼辦。 第三:代碼混亂,你會發現ioc/di不止能夠返回一個對象,一個函數,甚至能夠執行一個命令,而後錘子釘子錘子釘子。。。。。。
思想
思想是解決問題的根本
思想必須轉換成習慣
構建一套完整的思想體系是開發能力成熟的標誌
——《簡單之美》(前言)php
.html
「成功的軟件項目就是那些提交產物達到或超出客戶的預期的項目,並且開發過程符合時間和費用上的要求,結果在面對變化和調整時有彈性。」
——《面向對象分析與設計》(第3版)P.236程序員
術語介紹
——引用《Spring 2.0 技術手冊》林信良算法
非侵入性 No intrusive
-
框架的目標之一是非侵入性(No intrusive)數據庫
-
組件能夠直接拿到另外一個應用或框架之中使用編程
-
增長組件的可重用性(Reusability)segmentfault
容器(Container)
-
管理對象的生成、資源取得、銷燬等生命週期設計模式
-
創建對象與對象之間的依賴關係服務器
-
啓動容器後,全部對象直接取用,不用編寫任何一行代碼來產生對象,或是創建對象之間的依賴關係。session
IoC
-
控制反轉 Inversion of Control
-
依賴關係的轉移
-
依賴抽象而非實踐
DI
-
依賴注入 Dependency Injection
-
沒必要本身在代碼中維護對象的依賴
-
容器自動根據配置,將依賴注入指定對象
AOP
-
Aspect-oriented programming
-
面向方面編程
-
無需修改任何一行程序代碼,將功能加入至原先的應用程序中,也能夠在不修改任何程序的狀況下移除。
分層
表現層:提供服務,顯示信息。
領域層:邏輯,系統中真正的核心。
數據源層:與數據庫、消息系統、事務管理器及其它軟件包通訊。
——《企業應用架構模式》P.14
代碼演示IoC
假設應用程序有儲存需求,若直接在高層的應用程序中調用低層模塊API,致使應用程序對低層模塊產生依賴。
/** * 高層 */ class Business { private $writer; public function __construct() { $this->writer = new FloppyWriter(); } public function save() { $this->writer->saveToFloppy(); } } /** * 低層,軟盤存儲 */ class FloppyWriter { public function saveToFloppy() { echo __METHOD__; } } $biz = new Business(); $biz->save(); // FloppyWriter::saveToFloppy
假設程序要移植到另外一個平臺,而該平臺使用USB磁盤做爲存儲介質,則這個程序沒法直接重用,必須加以修改才行。本例因爲低層變化致使高層也跟着變化,很差的設計。
正如前方提到的
控制反轉 Inversion of Control
依賴關係的轉移
依賴抽象而非實踐
程序不該該依賴於具體的實現,而是要依賴抽像的接口。請看代碼演示
/** * 接口 */ interface IDeviceWriter { public function saveToDevice(); } /** * 高層 */ class Business { /** * @var IDeviceWriter */ private $writer; /** * @param IDeviceWriter $writer */ public function setWriter($writer) { $this->writer = $writer; } public function save() { $this->writer->saveToDevice(); } } /** * 低層,軟盤存儲 */ class FloppyWriter implements IDeviceWriter { public function saveToDevice() { echo __METHOD__; } } /** * 低層,USB盤存儲 */ class UsbDiskWriter implements IDeviceWriter { public function saveToDevice() { echo __METHOD__; } } $biz = new Business(); $biz->setWriter(new UsbDiskWriter()); $biz->save(); // UsbDiskWriter::saveToDevice $biz->setWriter(new FloppyWriter()); $biz->save(); // FloppyWriter::saveToDevice
控制權從實際的FloppyWriter轉移到了抽象的IDeviceWriter接口上,讓Business依賴於IDeviceWriter接口,且FloppyWriter、UsbDiskWriter也依賴於IDeviceWriter接口。
這就是IoC,面對變化,高層不用修改一行代碼,再也不依賴低層,而是依賴注入,這就引出了DI。
比較實用的注入方式有三種:
-
Setter injection 使用setter方法
-
Constructor injection 使用構造函數
-
Property Injection 直接設置屬性
事實上無論有多少種方法,都是IoC思想的實現而已,上面的代碼演示的是Setter方式的注入。
依賴注入容器 Dependency Injection Container
-
管理應用程序中的『全局』對象(包括實例化、處理依賴關係)。
-
能夠延時加載對象(僅用到時才建立對象)。
-
促進編寫可重用、可測試和鬆耦合的代碼。
理解了IoC和DI以後,就引起了另外一個問題,引用Phalcon文檔描述以下:
若是這個組件有不少依賴, 咱們須要建立多個參數的setter方法來傳遞依賴關係,或者創建一個多個參數的構造函數來傳遞它們,另外在使用組件前還要每次都建立依賴,這讓咱們的代碼像這樣不易維護
//建立依賴實例或從註冊表中查找 $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。爲了解決這樣的問題,咱們再次回到全局註冊表建立組件。無論怎麼樣,在建立對象以前,它增長了一個新的抽象層:
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); } }
瞬間,咱們又回到剛剛開始的問題了,咱們再次建立依賴實例在組件內部!咱們能夠繼續前進,找出一個每次能奏效的方法去解決這個問題。但彷佛一次又一次,咱們又回到了不實用的例子中。
一個實用和優雅的解決方法,是爲依賴實例提供一個容器。這個容器擔任全局的註冊表,就像咱們剛纔看到的那樣。使用依賴實例的容器做爲一個橋樑來獲取依賴實例,使咱們可以下降咱們的組件的複雜性:
class SomeComponent { protected $_di; public function __construct($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 Phalcon\DI(); //在容器中註冊一個db服務 $di->set('db', function() { return new Connection(array( "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->someTask();
這個組件如今能夠很簡單的獲取到它所須要的服務,服務採用延遲加載的方式,只有在須要使用的時候才初始化,這也節省了服務器資源。這個組件如今是高度解耦。例如,咱們能夠替換掉建立鏈接的方式,它們的行爲或它們的任何其餘方面,也不會影響該組件。
參考文章
-
Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
補充
不少代碼背後,都是某種哲學思想的體現。
如下引用《面向模式的軟件架構》卷1模式系統第六章模式與軟件架構
軟件架構支持技術(開發軟件時要遵循的基本原則)
-
抽象
-
封裝
-
信息隱藏
-
分離關注點
-
耦合與內聚
-
充分、完整、簡單
-
策略與實現分離
-
策略組件負責上下文相關決策,解讀信息的語義和含義,將衆多不一樣結果合併或選擇參數值
-
實現組件負責執行定義完整的算法,不須要做出與上下文相關的決策。上下文和解釋是外部的,一般由傳遞給組件的參數提供。
-
-
接口與實現分離
-
接口部分定義了組件提供的功能以及如何使用該組件。組件的客戶端能夠訪問該接口。
-
實現部分包含實現組件提供的功能的實際代碼,還可能包含僅供組件內部使用的函數和數據結構。組件的客戶端不能訪問其實現部分。
-
-
單個引用點
-
軟件系統中的任何元素都應只聲明和定義一次,避免不一致性問題。
10. 分而治之
-
軟件架構的非功能特性
-
可修改性
-
可維護性
-
可擴展性
-
重組
-
可移植性
-
-
互操做性
-
與其它系統或環境交互
-
-
效率
-
可靠性
-
容錯:發生錯誤時確保行爲正確並自行修復
-
健壯性:對應用程序進行保護,抵禦錯誤的使用方式和無效輸入,確保發生意外錯誤時處於指定狀態。
-
-
可測試性
-
可重用性
-
經過重用開發軟件
-
開發軟件時考慮重用
-