摘要:在中使用解耦,有兩種注入方式構(gòu)造函數(shù)注入屬性注入。對(duì)象的實(shí)例化解析依賴信息該方法實(shí)質(zhì)上就是通過(guò)的反射機(jī)制,通過(guò)類的構(gòu)造函數(shù)的參數(shù)分析他所依賴的單元。
有關(guān)概念 依賴倒置原則(Dependence Inversion Principle, DIP)
傳統(tǒng)軟件設(shè)計(jì)中,上層代碼依賴于下層代碼,當(dāng)下層出現(xiàn)變動(dòng)時(shí),上層也要相應(yīng)變化。
DIP的核心思想是:上層定義接口,下層實(shí)現(xiàn)這個(gè)接口,從而使的下層依賴于上層,降低耦合。
控制反轉(zhuǎn)(Inversion of Control, IoC)IoC是DIP的具體思路做法,IoC的核心是將類所依賴的下層單元的實(shí)例化過(guò)程交由第三方來(lái)實(shí)現(xiàn)。
一個(gè)簡(jiǎn)單的特征:類中不對(duì)所依賴的單元有諸如$component = new yiicomponentSomeClass()的實(shí)例化語(yǔ)句。
依賴注入(Dependence Injection, DI)DI是IoC的一種設(shè)計(jì)模式。
DI的核心是把類所依賴的單元的實(shí)例化過(guò)程,放到類的外面去實(shí)現(xiàn)。
控制反轉(zhuǎn)容器(IoC Container)當(dāng)項(xiàng)目比較大時(shí),依賴關(guān)系可能很復(fù)雜。而IoCC提供了動(dòng)態(tài)地創(chuàng)建、注入依賴單元,映射依賴關(guān)系等功能。Yii設(shè)計(jì)了一個(gè)yiidiContainer來(lái)實(shí)現(xiàn)了DI Container。
服務(wù)定位器(Service Locator)SL時(shí)IoC的另一種實(shí)現(xiàn)方式,其核心是把所有可能用到的依賴單元交給SL進(jìn)行實(shí)例化和操作,把類對(duì)依賴單元的依賴,轉(zhuǎn)換成類對(duì)SL的依賴。
Yii2通過(guò)DI容器,實(shí)現(xiàn)了SL。
依賴注入DI在web中,常見于使用第三方服務(wù)實(shí)現(xiàn)特定功能(例:發(fā)郵件,推微博)。
假設(shè)要實(shí)現(xiàn)當(dāng)訪客在博客上發(fā)表評(píng)論后,向博文的作者發(fā)送Email的功能,通常代碼如下:
// 為郵件服務(wù)定義抽象層 interface EmailSenderInterface{ public function send(); } // 定義Gmail服務(wù) class GmailSender implements EmailSenderInterface{ public function send() } // 定義評(píng)論類 class Comment extend yiidbActiveRecord{ private $_eEmailSender; public function init(){ $this->_eMailSender = GmailSender::getInstance(); } public function afterInsert(){ $this->_eMailSender->send(); } }
這個(gè)常見的設(shè)計(jì)方法有一個(gè)問(wèn)題:Comment對(duì)于GmailSender的依賴,突然有一天不用Gmail了,那么必須修改init里的實(shí)例化語(yǔ)句。
同時(shí),這個(gè)類的復(fù)用程度不高,下一個(gè)不用Gmail服務(wù)的項(xiàng)目,還需要再修改,或者直接去掉該郵件服務(wù)。
在Yii中使用DI解耦,有兩種注入方式:構(gòu)造函數(shù)注入、屬性注入。
構(gòu)造函數(shù)注入class Comment extend yiidbActiveRecord{ private $_eMailSender; public function __construct($emailSender){ $this->_eMailSender = $emailSender; } public function afterInsert(){ $this->_eMailSender->send(); } } // 實(shí)例化兩種不同的郵件服務(wù),都繼承了基類 $sender1 = new GmailSender(); $sender2 = new MyEmailSender(); $comment1 = new Comment($sender1); $comment1.save(); $comment2 = new Comment($sender2); $comment2.save();屬性注入
class Comment extend yiidbActiveRecord{ private $_eMailSender; public function setEmailSender($value){ $this->_eMailSender = $value; } public function afterInsert(){ $this->_eMailSender->send(); } }
實(shí)際上,依賴注入就是從外面,將實(shí)例打到內(nèi)部,從而完成整體的功能。
打入的方式有兩種,一種是初始化是通過(guò)傳參。另外一種是調(diào)用內(nèi)部set方法,將實(shí)例注入屬性,內(nèi)部方法會(huì)調(diào)用該屬性,進(jìn)而完成功能。
DI容器一個(gè)Web應(yīng)用的某一組件會(huì)依賴于若干單元,這些單元又有可能依賴于基本單元,從而形成依賴嵌套的情形。
那么,這些依賴單元的實(shí)例化、注入過(guò)程的代碼就會(huì)又長(zhǎng)又繁雜,前后關(guān)系也需要注意。
yiidiContainer,通過(guò)DI容器,可以更好的管理對(duì)象及對(duì)象的所有依賴,以及這些依賴的依賴,進(jìn)行實(shí)例化和配置。
DI容器中的內(nèi)容Yii使用yiidiInstance來(lái)表示容器中的東西。Yii還將這個(gè)類用于Service Locator。
該Instance本質(zhì)上是DI容器中對(duì)于某一個(gè)類實(shí)例的引用,它的代碼看起來(lái)并不復(fù)雜:
class Instance{ // 保存類名,借口名,別名 public $id; protected function __construct($id){} // 靜態(tài)方法創(chuàng)建一個(gè)Instance實(shí)例 public static function of($id){ return new static($id); } // 將引用解析成實(shí)際的對(duì)象,并確保這個(gè)對(duì)象的類型 public static function ensure($reference, $type = null, $container = null){} // 獲取這個(gè)實(shí)例所引用的實(shí)際對(duì)象,事實(shí)上它調(diào)用的是yiidiContainer::get() public function get($container = null){} }
該Instance:
表示的是容器中的內(nèi)容,代表的是對(duì)于實(shí)際對(duì)象的引用。
DI容器可以通過(guò)他獲取所引用的實(shí)際對(duì)象。
屬性id表示實(shí)例的類型.
DI容器的數(shù)據(jù)結(jié)構(gòu)// 用于保存單例Singleton對(duì)象,以對(duì)象類型為鍵 private $_singletons = []; // 用于保存依賴的定義,以對(duì)象類型為鍵 private $_definitions = []; // 用于保存構(gòu)造函數(shù)的參數(shù),以對(duì)象類型為鍵 private $_params = []; // 用于緩存ReflectionClass對(duì)象,以類名或接口名為鍵 private $_reflections = []; // 用于緩存依賴信息,以類名或接口名為鍵 private $_dependencies = [];注冊(cè)依賴
使用DI容器,首先要告訴容器,類型及類型之間的依賴關(guān)系,聲明這一關(guān)系的過(guò)程稱為注冊(cè)依賴。
使用:yiidiContainer::set() & yiidiContainer::setSinglton()
在DI容器中,依賴關(guān)系的定義是唯一的。 后定義的同名依賴,會(huì)覆蓋前面定義好的依賴。
對(duì)于 set() 而言,還要?jiǎng)h除 $_singleton[] 中的同名依賴。 對(duì)于 setSingleton() 而言,則要將 $_singleton[] 中的同名依賴設(shè)為 null , 表示定義了一個(gè)Singleton,但是并未實(shí)現(xiàn)化。
$container = new yiidiContainer; // 直接以一個(gè)類名注冊(cè)一個(gè)依賴 // $_definition["yiidbConnection"] = "yiidbConnection"; $container->set("yiidbConnection"); // 注冊(cè)一個(gè)接口,當(dāng)一個(gè)類依賴于該接口時(shí),定義中的類會(huì)自動(dòng)被實(shí)例化,并給有依賴需要的類使用 // $_definition["yiimailMailInterface"] = "yiiswiftmailerMailer"; $container->set("yiimailMailInterface", "yiiswiftmailerMailer"); // 注冊(cè)一個(gè)別名 $container->set("foo", "yiidbConnection"); // 用callable來(lái)注冊(cè)一個(gè)別名,每次引用這個(gè)別名時(shí),該callable都會(huì)被調(diào)用 $container->set("db", function($container, $params, $config){ return new yiidbConnectin($config); });
你可以這么理解:依賴的定義只是往特定的數(shù)據(jù)結(jié)構(gòu)中寫入有關(guān)的信息。
DI容器中裝了兩類實(shí)例,一種是單例,每次向容器索取單例類型的實(shí)例時(shí),得到的都是同一個(gè)實(shí)例; 另一類是普通實(shí)例,每次向容器索要普通類型的實(shí)例時(shí),容器會(huì)根據(jù)依賴信息創(chuàng)建一個(gè)新的實(shí)例給你。
對(duì)象的實(shí)例化 解析依賴信息yiidiContainer::getDependencies()
該方法實(shí)質(zhì)上就是通過(guò)PHP5的反射機(jī)制,通過(guò)類的構(gòu)造函數(shù)的參數(shù)分析他所依賴的單元。然后統(tǒng)統(tǒng)緩存起來(lái)備用。
另一個(gè)與解析依賴信息相關(guān)的方法就是 yiidiContainer::resolveDependencies() 。
$_reflections以類(接口、別名)名為鍵, 緩存了這個(gè)類(接口、別名)的ReflcetionClass。一經(jīng)緩存,便不會(huì)再更改。
$_dependencies以類(接口、別名)名為鍵,緩存了這個(gè)類(接口、別名)的依賴信息。
這兩個(gè)緩存數(shù)組都是在yiidiContainer::getDependencies()中完成。這個(gè)函數(shù)只是簡(jiǎn)單地向數(shù)組寫入數(shù)據(jù)。
經(jīng)過(guò)yiidiContainer::resolveDependencies()處理,DI容器會(huì)將依賴信息轉(zhuǎn)換成實(shí)例。 這個(gè)實(shí)例化的過(guò)程中,是向容器索要實(shí)例。也就是說(shuō),有可能會(huì)引起遞歸。
實(shí)例的創(chuàng)建yiidiContainer::build():
DI容器只支持yiiaseObject類。也就是說(shuō),你只能向DI容器索要 yiiaseObject 及其子類。 再換句話說(shuō),如果你想你的類可以放在DI容器里,那么必須繼承自 yiiaseObject 類。 但Yii中幾乎開發(fā)者在開發(fā)過(guò)程中需要用到的類,都是繼承自這個(gè)類。 一個(gè)例外就是上面提到的 yiidiInstance 類。但這個(gè)類是供Yii框架自己使用的,開發(fā)者無(wú)需操作這個(gè)類。
遞歸獲取依賴單元的依賴在于dependencies = $this->resolveDependencies($dependencies, $reflection)中。
getDependencies() 和 resolveDependencies() 為 build() 所用。 也就是說(shuō),只有在創(chuàng)建實(shí)例的過(guò)程中,DI容器才會(huì)去解析依賴信息、緩存依賴信息。
容器內(nèi)容實(shí)例化過(guò)程獲取依賴實(shí)例化對(duì)象使用yiidiContainer::get(),
在整個(gè)實(shí)例化過(guò)程中,一共有兩個(gè)地方會(huì)產(chǎn)生遞歸:一是 get() , 二是 build() 中的 resolveDependencies() 。
實(shí)例namespace appmodels; use yiiaseObject; use yiidbConnection; interface UserFinderInterface{ function findUser(); } class UserFinder extends Object implements UserFinderInterface{ public $db; // 依賴于Connection public function __construct(Connection $db, $config = []){ $this->db = $db; parent::__construct($config); } pubic function findUser(){} } class UserLister extends Object{ public $finder; // 依賴接口 public function __construct(UserFinderInterface $finder, $config = []){ $this->finder = $finder; parent::__construct($config); } }
一般做法:
$db = new yiidbConnection(["dsn" => "..."]); $finder = new UserFinder($db); $lister = new UserLister($finder);
團(tuán)隊(duì)開發(fā)的時(shí)候,很多類需要制定為單例模式,否則N個(gè)模塊有N個(gè)服務(wù)就。。
上部代碼改成DI容器
use yiidiContainer; $container = new Container; $container->set("yiidbConnection", [...]); $container->set("appmodelsUserFinderInterface", [ "class" => "appmodelsUserFinder", ]); $container->set("userLister", "appmodelsUserLister"); // 獲取該別名class的實(shí)例 $lister = $container->get("userLister");
DI容器維護(hù)了兩個(gè)緩存數(shù)組 $_reflections 和 $_dependencies 。這兩個(gè)數(shù)組只寫入一次,就可以無(wú)限次使用。 因此,減少了對(duì)ReflectionClass的使用,提高了DI容器解析依賴和獲取實(shí)例的效率。
但是,對(duì)于典型的Web應(yīng)用而言, 有許多模塊其實(shí)應(yīng)當(dāng)注冊(cè)為單例的,比如上面的 yiidbConnection。一個(gè)Web應(yīng)用一般使用一個(gè)數(shù)據(jù)庫(kù)連接,特殊情況下會(huì)用多幾個(gè),所以這些數(shù)據(jù)庫(kù)連接一般是給定不同別名加以區(qū)分后, 分別以單例形式放在容器中的。因此,實(shí)際獲取實(shí)例時(shí),步驟會(huì)簡(jiǎn)單得。對(duì)于單例, 在第一次get()時(shí),直接就返回了。而且,省去不重復(fù)構(gòu)造實(shí)例的過(guò)程。
參考
http://martinfowler.com/articles/injection.html
http://www.digpage.com/di.html
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21468.html
摘要:官網(wǎng)源碼解讀號(hào)外號(hào)外歡迎大家我們開發(fā)組定了一個(gè)就線下聚一次的小目標(biāo)里面的框架算是非常重的了這里的重先不具體到性能層面主要是框架的設(shè)計(jì)思想和框架集成的服務(wù)讓框架可以既可以快速解決很多問(wèn)題又可以輕松擴(kuò)展中的框架有在應(yīng)該無(wú)出其右了這次解讀的源碼 官網(wǎng): https://www.swoft.org/源碼解讀: http://naotu.baidu.com/file/8... 號(hào)外號(hào)外, 歡迎大...
摘要:調(diào)用方法創(chuàng)建類得實(shí)例化對(duì)象,實(shí)際上又調(diào)用了依賴注入容器獲取每一個(gè)類的實(shí)例化對(duì)象。依賴注入容器自動(dòng)解決待實(shí)例化類的依賴關(guān)系,并返回待實(shí)例化類的實(shí)例對(duì)象。 以下是Yii2源碼中,ServiceLocator(服務(wù)定位器)與Container(依賴注入容器)的關(guān)系解析圖。 一句話總結(jié) Application繼承了ServiceLocator,是一個(gè)服務(wù)器定位器,ServiceLocator用...
摘要:反射簡(jiǎn)介參考官方簡(jiǎn)介的話,具有完整的反射,添加了對(duì)類接口函數(shù)方法和擴(kuò)展進(jìn)行反向工程的能力。此外,反射提供了方法來(lái)取出函數(shù)類和方法中的文檔注釋。 反射簡(jiǎn)介 參考官方簡(jiǎn)介的話,PHP 5 具有完整的反射 API,添加了對(duì)類、接口、函數(shù)、方法和擴(kuò)展進(jìn)行反向工程的能力。 此外,反射 API 提供了方法來(lái)取出函數(shù)、類和方法中的文檔注釋。 YII2框架中示例 對(duì)于yii2框架,應(yīng)該都知道di容器,...
摘要:行為所要響應(yīng)的事件重載方法,表示這個(gè)行為將對(duì)類何種事件進(jìn)行何種反饋。行為用的最多的,也是對(duì)于各種事件的響應(yīng)。當(dāng)出現(xiàn)命名沖突時(shí),行為會(huì)自行排除沖突,自動(dòng)使用先綁定的行為。目前還沒有能支持行為。 Yii基礎(chǔ) 行為(Behavior) 行為(behavior)可以在不修改現(xiàn)有類的情況下,對(duì)類的功能進(jìn)行擴(kuò)充。 通過(guò)將行為綁定到一個(gè)類,可以使類具有行為本身所定義的屬性和方法,就好像類本來(lái)就有這些...
摘要:環(huán)境需要了解一下一個(gè)純粹的與本地環(huán)境密切相關(guān)的配置項(xiàng)。對(duì)于配置項(xiàng)以數(shù)組進(jìn)行組織。數(shù)組元素表示將要?jiǎng)?chuàng)建的對(duì)象的完整類名。數(shù)組元素表示指定為屬性的初始值為。數(shù)組元素表示將綁定到對(duì)象的事件中。對(duì)于形式配置項(xiàng),視配置值為一個(gè)事件,綁定到上。 環(huán)境 需要了解一下cookieValidationKey:一個(gè)純粹的、與本地環(huán)境密切相關(guān)的配置項(xiàng)。 但是,在有些情況下,cookieValidationK...
閱讀 3263·2021-09-22 15:58
閱讀 1727·2019-08-30 14:17
閱讀 1734·2019-08-28 18:05
閱讀 1515·2019-08-26 13:33
閱讀 696·2019-08-26 12:20
閱讀 617·2019-08-26 12:18
閱讀 3200·2019-08-26 11:59
閱讀 1414·2019-08-26 10:36