摘要:依賴注入容器管理應(yīng)用程序中的全局對象包括實(shí)例化處理依賴關(guān)系。為了解決這樣的問題,我們再次回到全局注冊表創(chuàng)建組件。參考文章程序員如何理解依賴注入容器補(bǔ)充很多代碼背后,都是某種哲學(xué)思想的體現(xiàn)。
思想
思想是解決問題的根本
思想必須轉(zhuǎn)換成習(xí)慣
構(gòu)建一套完整的思想體系是開發(fā)能力成熟的標(biāo)志
——《簡單之美》(前言)
.
術(shù)語介紹“成功的軟件項(xiàng)目就是那些提交產(chǎn)物達(dá)到或超出客戶的預(yù)期的項(xiàng)目,而且開發(fā)過程符合時間和費(fèi)用上的要求,結(jié)果在面對變化和調(diào)整時有彈性?!?br>——《面向?qū)ο蠓治雠c設(shè)計》(第3版)P.236
——引用《Spring 2.0 技術(shù)手冊》林信良
非侵入性 No intrusive框架的目標(biāo)之一是非侵入性(No intrusive)
組件可以直接拿到另一個應(yīng)用或框架之中使用
增加組件的可重用性(Reusability)
容器(Container)管理對象的生成、資源取得、銷毀等生命周期
建立對象與對象之間的依賴關(guān)系
啟動容器后,所有對象直接取用,不用編寫任何一行代碼來產(chǎn)生對象,或是建立對象之間的依賴關(guān)系。
IoC控制反轉(zhuǎn) Inversion of Control
依賴關(guān)系的轉(zhuǎn)移
依賴抽象而非實(shí)踐
DI依賴注入 Dependency Injection
不必自己在代碼中維護(hù)對象的依賴
容器自動根據(jù)配置,將依賴注入指定對象
AOPAspect-oriented programming
面向方面編程
無需修改任何一行程序代碼,將功能加入至原先的應(yīng)用程序中,也可以在不修改任何程序的情況下移除。
分層代碼演示IoC表現(xiàn)層:提供服務(wù),顯示信息。
領(lǐng)域?qū)樱哼壿嫞到y(tǒng)中真正的核心。
數(shù)據(jù)源層:與數(shù)據(jù)庫、消息系統(tǒng)、事務(wù)管理器及其它軟件包通信。
——《企業(yè)應(yīng)用架構(gòu)模式》P.14
假設(shè)應(yīng)用程序有儲存需求,若直接在高層的應(yīng)用程序中調(diào)用低層模塊API,導(dǎo)致應(yīng)用程序?qū)Φ蛯幽K產(chǎn)生依賴。
/** * 高層 */ 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
假設(shè)程序要移植到另一個平臺,而該平臺使用USB磁盤作為存儲介質(zhì),則這個程序無法直接重用,必須加以修改才行。本例由于低層變化導(dǎo)致高層也跟著變化,不好的設(shè)計。
正如前方提到的
控制反轉(zhuǎn) Inversion of Control
依賴關(guān)系的轉(zhuǎn)移
依賴抽象而非實(shí)踐
程序不應(yīng)該依賴于具體的實(shí)現(xiàn),而是要依賴抽像的接口。請看代碼演示
/** * 接口 */ 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
控制權(quán)從實(shí)際的FloppyWriter轉(zhuǎn)移到了抽象的IDeviceWriter接口上,讓Business依賴于IDeviceWriter接口,且FloppyWriter、UsbDiskWriter也依賴于IDeviceWriter接口。
這就是IoC,面對變化,高層不用修改一行代碼,不再依賴低層,而是依賴注入,這就引出了DI。
比較實(shí)用的注入方式有三種:
Setter injection 使用setter方法
Constructor injection 使用構(gòu)造函數(shù)
Property Injection 直接設(shè)置屬性
事實(shí)上不管有多少種方法,都是IoC思想的實(shí)現(xiàn)而已,上面的代碼演示的是Setter方式的注入。
依賴注入容器 Dependency Injection Container管理應(yīng)用程序中的『全局』對象(包括實(shí)例化、處理依賴關(guān)系)。
可以延時加載對象(僅用到時才創(chuàng)建對象)。
促進(jìn)編寫可重用、可測試和松耦合的代碼。
理解了IoC和DI之后,就引發(fā)了另一個問題,引用Phalcon文檔描述如下:
如果這個組件有很多依賴, 我們需要創(chuàng)建多個參數(shù)的setter方法??來傳遞依賴關(guān)系,或者建立一個多個參數(shù)的構(gòu)造函數(shù)來傳遞它們,另外在使用組件前還要每次都創(chuàng)建依賴,這讓我們的代碼像這樣不易維護(hù)
//創(chuàng)建依賴實(shí)例或從注冊表中查找 $connection = new Connection(); $session = new Session(); $fileSystem = new FileSystem(); $filter = new Filter(); $selector = new Selector(); //把實(shí)例作為參數(shù)傳遞給構(gòu)造函數(shù) $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector); // ... 或者使用setter $some->setConnection($connection); $some->setSession($session); $some->setFileSystem($fileSystem); $some->setFilter($filter); $some->setSelector($selector);
假設(shè)我們必須在應(yīng)用的不同地方使用和創(chuàng)建這些對象。如果當(dāng)你永遠(yuǎn)不需要任何依賴實(shí)例時,你需要去刪掉構(gòu)造函數(shù)的參數(shù),或者去刪掉注入的setter。為了解決這樣的問題,我們再次回到全局注冊表創(chuàng)建組件。不管怎么樣,在創(chuàng)建對象之前,它增加了一個新的抽象層:
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); } }
瞬間,我們又回到剛剛開始的問題了,我們再次創(chuàng)建依賴實(shí)例在組件內(nèi)部!我們可以繼續(xù)前進(jìn),找出一個每次能奏效的方法去解決這個問題。但似乎一次又一次,我們又回到了不實(shí)用的例子中。
一個實(shí)用和優(yōu)雅的解決方法,是為依賴實(shí)例提供一個容器。這個容器擔(dān)任全局的注冊表,就像我們剛才看到的那樣。使用依賴實(shí)例的容器作為一個橋梁來獲取依賴實(shí)例,使我們能夠降低我們的組件的復(fù)雜性:
class SomeComponent { protected $_di; public function __construct($di) { $this->_di = $di; } public function someDbTask() { // 獲得數(shù)據(jù)庫連接實(shí)例 // 總是返回一個新的連接 $connection = $this->_di->get("db"); } public function someOtherDbTask() { // 獲得共享連接實(shí)例 // 每次請求都返回相同的連接實(shí)例 $connection = $this->_di->getShared("db"); // 這個方法也需要一個輸入過濾的依賴服務(wù) $filter = $this->_di->get("filter"); } } $di = new PhalconDI(); //在容器中注冊一個db服務(wù) $di->set("db", function() { return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); }); //在容器中注冊一個filter服務(wù) $di->set("filter", function() { return new Filter(); }); //在容器中注冊一個session服務(wù) $di->set("session", function() { return new Session(); }); //把傳遞服務(wù)的容器作為唯一參數(shù)傳遞給組件 $some = new SomeComponent($di); $some->someTask();
這個組件現(xiàn)在可以很簡單的獲取到它所需要的服務(wù),服務(wù)采用延遲加載的方式,只有在需要使用的時候才初始化,這也節(jié)省了服務(wù)器資源。這個組件現(xiàn)在是高度解耦。例如,我們可以替換掉創(chuàng)建連接的方式,它們的行為或它們的任何其他方面,也不會影響該組件。
參考文章PHP程序員如何理解依賴注入容器(dependency injection container)
http://docs.phalconphp.com/zh/latest/reference/di.html
What is Dependency Injection? Fabien Potencier
Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
補(bǔ)充很多代碼背后,都是某種哲學(xué)思想的體現(xiàn)。
以下引用《面向模式的軟件架構(gòu)》卷1模式系統(tǒng)第六章模式與軟件架構(gòu)
軟件架構(gòu)支持技術(shù)(開發(fā)軟件時要遵循的基本原則)抽象
封裝
信息隱藏
分離關(guān)注點(diǎn)
耦合與內(nèi)聚
充分、完整、簡單
策略與實(shí)現(xiàn)分離
策略組件負(fù)責(zé)上下文相關(guān)決策,解讀信息的語義和含義,將眾多不同結(jié)果合并或選擇參數(shù)值
實(shí)現(xiàn)組件負(fù)責(zé)執(zhí)行定義完整的算法,不需要作出與上下文相關(guān)的決策。上下文和解釋是外部的,通常由傳遞給組件的參數(shù)提供。
接口與實(shí)現(xiàn)分離
接口部分定義了組件提供的功能以及如何使用該組件。組件的客戶端可以訪問該接口。
實(shí)現(xiàn)部分包含實(shí)現(xiàn)組件提供的功能的實(shí)際代碼,還可能包含僅供組件內(nèi)部使用的函數(shù)和數(shù)據(jù)結(jié)構(gòu)。組件的客戶端不能訪問其實(shí)現(xiàn)部分。
單個引用點(diǎn)
軟件系統(tǒng)中的任何元素都應(yīng)只聲明和定義一次,避免不一致性問題。
10. 分而治之
可修改性
可維護(hù)性
可擴(kuò)展性
重組
可移植性
互操作性
與其它系統(tǒng)或環(huán)境交互
效率
可靠性
容錯:發(fā)生錯誤時確保行為正確并自行修復(fù)
健壯性:對應(yīng)用程序進(jìn)行保護(hù),抵御錯誤的使用方式和無效輸入,確保發(fā)生意外錯誤時處于指定狀態(tài)。
可測試性
可重用性
通過重用開發(fā)軟件
開發(fā)軟件時考慮重用
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/20888.html
摘要:代碼這就是控制反轉(zhuǎn)模式。是變量有默認(rèn)值則設(shè)置默認(rèn)值是一個類,遞歸解析有默認(rèn)值則返回默認(rèn)值從容器中取得以上代碼的原理參考官方文檔反射,具有完整的反射,添加了對類接口函數(shù)方法和擴(kuò)展進(jìn)行反向工程的能力。 PHP程序員如何理解依賴注入容器(dependency injection container) 背景知識 傳統(tǒng)的思路是應(yīng)用程序用到一個Foo類,就會創(chuàng)建Foo類并調(diào)用Foo類的方法,假如這...
摘要:前言最近在使用框架,看了下他的源碼,發(fā)現(xiàn)有很多地方也用到了依賴注入控制反轉(zhuǎn),覺得有必要和大家簡單聊一聊什么是依賴注入以及怎么使用它。概念依賴注入和控制反轉(zhuǎn)是對同一件事情的不同描述,從某個方面講,就是它們描述的角度不同。 前言 最近在使用ThinkPHP5框架,看了下他的源碼,發(fā)現(xiàn)有很多地方也用到了依賴注入(控制反轉(zhuǎn)),覺得有必要和大家簡單聊一聊什么是依賴注入以及怎么使用它。 簡介 I...
摘要:標(biāo)量參數(shù)關(guān)聯(lián)傳值依賴是自動解析注入的,剩余的標(biāo)量參數(shù)則可以通過關(guān)聯(lián)傳值,這樣比較靈活,沒必要把默認(rèn)值的參數(shù)放在函數(shù)參數(shù)最尾部。 更新:github(給個小星星呀) -- 2018-4-11:優(yōu)化服務(wù)綁定方法 ::bind 的類型檢查模式 借助 PHP 反射機(jī)制實(shí)現(xiàn)的一套 依賴自動解析注入 的 IOC/DI 容器,可以作為 Web MVC 框架 的應(yīng)用容器 1、依賴的自動注入:你只需要...
摘要:當(dāng)你需要用到某個對象時,就可以使用如下代碼從容器中獲取獲取容器獲取容器中的對象管理方式的所創(chuàng)建與存儲的對象,我們稱之為屬性注入方式管理注解方式屬性注入注解方式 好處 IoC / DI,方便解耦 AOP 面向切面編程 聲明式事務(wù) 方便程序測試 方便集成其他優(yōu)秀框架 IoC 與 DI IoC 與 DI 的關(guān)系 IoC控制反轉(zhuǎn):主要是指【創(chuàng)建對象】這件事交給Spring進(jìn)行處理,無需開發(fā)...
摘要:服務(wù)本省作為一個高層類,對外提供訪問,卻受制于提供具體服務(wù)的服務(wù)提供者定義的實(shí)現(xiàn),高層模塊依賴底層模塊實(shí)現(xiàn),違背了依賴倒置原則。遵循依賴倒置原則的例子場景同介紹中場景。 1. 名詞介紹 OOD,面向?qū)ο笤O(shè)計 DIP,依賴倒置(軟件設(shè)計原則) IOC,控制反轉(zhuǎn)(軟件設(shè)計模式) DI,依賴注入 IOC Container,控制反轉(zhuǎn)容器,也是依賴注入容器 2. 組成部分 服務(wù)清單(功能...
閱讀 1454·2023-04-25 17:05
閱讀 3169·2021-11-19 09:40
閱讀 3972·2021-11-18 10:02
閱讀 1909·2021-09-23 11:45
閱讀 3176·2021-08-20 09:36
閱讀 2933·2021-08-13 15:07
閱讀 1287·2019-08-30 15:55
閱讀 2630·2019-08-30 14:11