Dependency Injection-依賴注入詳解

      網友投稿 963 2022-05-29

      依賴注入是目前很多優秀框架都在使用的一個設計模式。Java的開發框架如Spring在用,PHP的Laravel/Phalcon/Symfony等也在用。好多不同語言的框架,設計思想大同小異,相互借鑒參考。熟悉了一個語言的開發框架,其它不同的框架甚至不同語言的開發框架,往往也很容易從設計理念和概念上理解。不過,有些語言因為設計特色,一些設計模式反而看似消失不見了。其實是融入了語言里面,不易察覺。我看見過這么一句話:“設計模式是編程語言固有缺陷的產物”。有一個討論在這里:Why is IoC / DI not common in Python?

      Dependency Injection 常常簡稱為:DI。它是實現控制反轉(Inversion of Control – IoC)的一個模式。有一本依賴注入詳解的書在這里:Dependency Injection 。它的本質目的是解耦,保持軟件組件之間的松散耦合,為設計開發帶來靈活性。

      這里借用一套PHP代碼的演化過程,解釋依賴注入模式的出現過程。代碼來自Phalcon框架文檔。個人感覺,從演化出發,最能達成理解的目標,就如同數學推理一樣讓人信服,自然而然。想當年,我研究Windows時代的COM技術體系,看到有一本書也是這么做的 – Dan Box的《COM本質論》第1-2章,闡述了從Dll到COM組件的設計過程。

      假設我們要開發一套組件,這個組件干啥目前并不重要,不過它需要連接數據庫。最簡單的實現,當然是把數據庫的配置信息寫在組件里面。

      class?SomeComponent{????/** ?????*?The?instantiation?of?the?connection?is?hardcoded?inside ?????*?the?component,?therefore?it's?difficult?replace?it?externally ?????*?or?change?its?behavior ?????*/ ????public?function?someDbTask() ????{ ????????$connection?=?new?Connection( ????????????[????????????????"host"?????=>?"localhost",????????????????"username"?=>?"root",????????????????"password"?=>?"secret",????????????????"dbname"???=>?"invo", ????????????] ????????);? ????????//?... ????} } ? $some?=?new?SomeComponent(); ? $some->someDbTask();

      但是這么干問題很大,屬于“代碼的壞味道”。因為數據庫配置寫死了,完全沒有靈活性可言。這也給后面的測試/部署/安全帶來了隱患。為了解決這個問題,我們試試把配置信息拿出去,從外面傳進來。

      class?SomeComponent{????protected?$_connection;? ????/** ?????*?Sets?the?connection?externally ?????*/ ????public?function?setConnection($connection) ????{????????$this->_connection?=?$connection; ????}? ????public?function?someDbTask() ????{ ????????$connection?=?$this->_connection;? ????????//?... ????} } ? $some?=?new?SomeComponent();? //?Create?the?connection$connection?=?new?Connection( ????[????????"host"?????=>?"localhost",????????"username"?=>?"root",????????"password"?=>?"secret",????????"dbname"???=>?"invo", ????] );

      好一點。不過如果我們在很多地方都要用這個組件,那么意味著每次用的時候,都要創建這么一個連接配置對象,不僅冗余,而且難以變更和管理。我們把這個連接配置對象單獨放在一個地方管理,DRY原則。

      class?Registry{????/** ?????*?Returns?the?connection ?????*/ ????public?static?function?getConnection() ????{????????return?new?Connection( ????????????[????????????????"host"?????=>?"localhost",????????????????"username"?=>?"root",????????????????"password"?=>?"secret",????????????????"dbname"???=>?"invo", ? ???????????] ????????); ????} }? class?SomeComponent{????protected?$_connection;? ????/** ?????*?Sets?the?connection?externally ?????*/ ????public?function?setConnection($connection) ????{????????$this->_connection?=?$connection; ????}? ????public?function?someDbTask() ????{ ????????$connection?=?$this->_connection;? ????????//?... ????} } ? $some?=?new?SomeComponent();? //?Pass?the?connection?defined?in?the?registry$some->setConnection(Registry::getConnection()); ? $some->someDbTask();

      可行。不過有個問題,連接對象每次使用都是重復創建,浪費資源。再改一下,改成共享式,近似于單件模式。

      class?Registry{????protected?static?$_connection;? ????/** ?????*?Creates?a?connection ?????*/ ????protected?static?function?_createConnection() ????{????????return?new?Connection( ????????????[????????????????"host"?????=>?"localhost",????????????????"username"?=>?"root",????????????????"password"?=>?"secret",????????????????"dbname"???=>?"invo", ????????????] ????????); ????}? ????/** ?????*?Creates?a?connection?only?once?and?returns?it ?????*/ ????public?static?function?getSharedConnection() ????{????????if?(self::$_connection?===?null)?{????????????self::$_connection?=?self::_createConnection(); ????????}? ????????return?self::$_connection; ????}? ????/** ?????*?Always?returns?a?new?connection ?????*/ ????public?static?function?getNewConnection() ????{????????return?self::_createConnection(); ????} }? class?SomeComponent{????protected?$_connection;? ????/** ?????*?Sets?the?connection?externally ?????*/ ????public?function?setConnection($connection) ????{????????$this->_connection?=?$connection; ????}? ????/** ?????*?This?method?always?needs?the?shared?connection ?????*/ ????public?function?someDbTask() ????{ ????????$connection?=?$this->_connection;? ????????//?... ????}? ????/** ?????*?This?method?always?needs?a?new?connection ?????*/ ????public?function?someOtherDbTask($connection) ????{ ? ????} } ? $some?=?new?SomeComponent();? //?This?injects?the?shared?connection$some->setConnection( ????Registry::getSharedConnection() ); ? $some->someDbTask();? //?Here,?we?always?pass?a?new?connection?as?parameter$some->someOtherDbTask( ????Registry::getNewConnection() );

      這就是“依賴注入”模式了,它解決了組件的依賴項和組件之間的過度耦合問題。不過還有個麻煩:如果這個組件依賴項很多怎么辦?每次都要創建并設置一大堆依賴項。

      //?Create?the?dependencies?or?retrieve?them?from?the?registry$connection?=?new?Connection(); $session????=?new?Session(); $fileSystem?=?new?FileSystem(); $filter?????=?new?Filter(); $selector???=?new?Selector();? //?Pass?them?as?constructor?parameters$some?=?new?SomeComponent($connection,?$session,?$fileSystem,?$filter,?$selector);? //?...?Or?using?setters$some->setConnection($connection); $some->setSession($session); $some->setFileSystem($fileSystem); $some->setFilter($filter); $some->setSelector($selector);

      每次使用這個組件,都要創建一堆附加的依賴項。如果以后我們修改組件依賴,那么必須挨個改掉。代碼的壞味道又來了。再改。

      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); ????} }

      估計好多人走到這一步就會停下腳步了。代碼用個工廠模式不就行了嘛。可是你對比下開頭的代碼,組件和它的依賴項的耦合不就又來了么?現在,問題又回到開頭了。

      一個更好的辦法是使用依賴注入容器。它就如同一個全局的注冊表,像橋一樣獲取依賴項,并解耦。

      use?Phalcon\Di;use?Phalcon\DiInterface;? class?SomeComponent{????protected?$_di;? ????public?function?__construct(DiInterface?$di) ????{????????$this->_di?=?$di; ????}? ????public?function?someDbTask() ????{????????//?Get?the?connection?service ????????//?Always?returns?a?new?connection ????????$connection?=?$this->_di->get("db"); ????}? ????public?function?someOtherDbTask() ????{????????//?Get?a?shared?connection?service, ????????//?this?will?return?the?same?connection?every?time ????????$connection?=?$this->_di->getShared("db");? ????????//?This?method?also?requires?an?input?filtering?service ???????? ????$filter?=?$this->_di->get("filter"); ????} } ? $di?=?new?Di();? //?Register?a?"db"?service?in?the?container$di->set(????"db",????function?()?{????????return?new?Connection( ????????????[????????????????"host"?????=>?"localhost",????????????????"username"?=>?"root",????????????????"password"?=>?"secret",????????????????"dbname"???=>?"invo", ????????????] ????????); ????} );? //?Register?a?"filter"?service?in?the?container$di->set(????"filter",????function?()?{????????return?new?Filter(); ????} );? //?Register?a?"session"?service?in?the?container$di->set(????"session",????function?()?{????????return?new?Session(); ????} );? //?Pass?the?service?container?as?unique?parameter$some?=?new?SomeComponent($di); ? $some->someDbTask();”

      問題解決。獲取依賴項只要通過DI容器接口操作,不需要的部分甚至都不會創建,節約了資源。

      在Java的Spring框架里面,依賴注入和控制反轉設計思想是近似的,道理相同但是實現不同。因為編程語言各有各的設計特點可以利用。

      Dependency Injection-依賴注入詳解

      Spring框架的依賴注入容器接口是:ApplicationContext.

      ApplicationContext?context ??=?new?ClassPathXmlApplicationContext("applicationContext.xml");

      但是Spring使用DI,有好幾種方法,比如注解式,利用了語言的功能。

      具體解釋參考這篇文章,不翻譯了。

      Intro to Inversion of Control and Dependency Injection with Spring

      容器 Spring

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:華為發布數字基礎設施開源操作系統歐拉
      下一篇:【軟通鯤鵬云最佳實踐36】puppet-agent-5.5.1 鯤鵬云服務器安裝實踐
      相關文章
      久久精品蜜芽亚洲国产AV| 毛茸茸bbw亚洲人| 亚洲国产精品高清久久久| 亚洲精品无码激情AV| 爱情岛亚洲论坛在线观看 | 久久亚洲精品中文字幕三区| 亚洲精品WWW久久久久久| 国产AV无码专区亚洲AV蜜芽| 亚洲日韩精品无码专区加勒比| 在线观看日本亚洲一区| 亚洲熟女综合一区二区三区| 亚洲an日韩专区在线| 亚洲13又紧又嫩又水多| 亚洲黄页网在线观看| 亚洲一日韩欧美中文字幕在线| 亚洲最大的成人网站| 亚洲性色精品一区二区在线| 最新亚洲卡一卡二卡三新区| 亚洲色中文字幕在线播放| 亚洲精品国产av成拍色拍| 国产精品亚洲专区无码牛牛| 日韩精品电影一区亚洲| 国产成人精品亚洲一区| 亚洲午夜无码AV毛片久久| 亚洲综合伊人久久综合| 亚洲va久久久噜噜噜久久天堂| 亚洲AV中文无码乱人伦下载| 亚洲欧洲日韩不卡| 亚洲精品电影在线| 亚洲手机中文字幕| 中文字幕在线观看亚洲日韩| 亚洲AV无码男人的天堂| 久久久久亚洲AV无码专区桃色| 亚洲另类少妇17p| 亚洲老妈激情一区二区三区| 亚洲AV无码一区二区乱子伦 | 国产亚洲成归v人片在线观看| 亚洲熟妇少妇任你躁在线观看无码| 久久久久亚洲AV成人网人人网站| 亚洲国产精品无码久久久秋霞2| 亚洲AV日韩精品久久久久|