本文章轉載自: https://segmentfault.com/a/1190000005602011php
最近在研究php的lumen框架和phalcon框架,這兩個框架的底層架構都用到了IoC,DI,關於這兩個概念本身一直沒理解更清晰,找到一篇寫得很是好的博文,在此作個備份記錄。程序員
依賴倒置原則(DIP)(Dependency Inversion Principle):一種軟件架構設計的原則(抽象概念)。數據庫
控制反轉(IoC)(Inversion of Control):一種反轉流、依賴和接口的方式(DIP的具體實現方式)。segmentfault
依賴注入(DI)(Dependency Injection):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)。設計模式
IoC容器 :依賴注入的框架,用來映射依賴,管理對象建立和生存週期(DI框架)。服務器
依賴倒置原則,它轉換了依賴,高層模塊不依賴於低層模塊的實現,而低層模塊依賴於高層模塊定義的接口。通俗的講,就是高層模塊定義接口,低層模塊負責實現。session
Bob Martins對DIP的定義:
高層模塊不該依賴於低層模塊,二者應該依賴於抽象。
抽象不該該依賴於實現,實現應該依賴於抽象。架構
從上圖中,咱們發現高層模塊的類依賴於低層模塊的接口。所以,低層模塊須要考慮到全部的接口。若是有新的低層模塊類出現時,高層模塊須要修改代碼,來實現新的低層模塊的接口。這樣,就破壞了開放封閉原則。框架
在這個圖中,咱們發現高層模塊定義了接口,將再也不直接依賴於低層模塊,低層模塊負責實現高層模塊定義的接口。這樣,當有新的低層模塊實現時,不須要修改高層模塊的代碼。函數
由此,咱們能夠總結出使用DIP的優勢:
系統更柔韌:能夠修改一部分代碼而不影響其餘模塊。
系統更健壯:能夠修改一部分代碼而不會讓系統崩潰。
系統更高效:組件鬆耦合,且可複用,提升開發效率。
DIP是一種 軟件設計原則,它僅僅告訴你兩個模塊之間應該如何依賴,可是它並無告訴如何作。IoC則是一種 軟件設計模式,它告訴你應該如何作,來解除相互依賴模塊的耦合。控制反轉(IoC),它爲相互依賴的組件提供抽象,將依賴(低層模塊)對象的得到交給第三方(系統)來控制,即依賴對象不在被依賴模塊的類中直接經過new來獲取。
軟件設計原則:原則爲咱們提供指南,它告訴咱們什麼是對的,什麼是錯的。它不會告訴咱們如何解決問題。它僅僅給出一些準則,以便咱們能夠設計好的軟件,避免不良的設計。一些常見的原則,好比DRY、OCP、DIP等。
軟件設計模式:模式是在軟件開發過程當中總結得出的一些可重用的解決方案,它能解決一些實際的問題。一些常見的模式,好比工廠模式、單例模式等等。
IoC有2種常見的實現方式:依賴注入和服務定位。主要的實現方式依賴注入。
控制反轉(IoC)一種重要的方式,就是將依賴對象的建立和綁定轉移到被依賴對象類的外部來實現。
依賴注入(DI),它提供一種機制,將須要依賴(低層模塊)對象的引用傳遞給被依賴(高層模塊)對象。
class Bim { public function doSomething() { echo __METHOD__, '|'; } } class Bar { public function doSomething() { $bim = new Bim(); $bim->doSomething(); echo __METHOD__, '|'; } } class Foo { public function doSomething() { $bar = new Bar(); $bar->doSomething(); echo __METHOD__; } } $foo = new Foo(); $foo->doSomething(); //Bim::doSomething|Bar::doSomething|Foo::doSomething
缺點:類與類之間的耦合程度過高,Foo必須依賴Bar,沒有Bar,Foo就不能工做,這種方案最不可取。
構造函數函數注入,毫無疑問經過構造函數傳遞依賴。所以,構造函數的參數必然用來接收一個依賴對象。那麼參數的類型是什麼呢?具體依賴對象的類型?仍是一個抽象類型?根據DIP原則,咱們知道高層模塊不該該依賴於低層模塊,二者應該依賴於抽象。那麼構造函數的參數應該是一個抽象類型。
class Bim { public function doSomething() { echo __METHOD__, '|'; } } class Bar { private $bim; public function __construct(Bim $bim) { $this->bim = $bim; } public function doSomething() { $this->bim->doSomething(); echo __METHOD__, '|'; } } class Foo { private $bar; public function __construct(Bar $bar) { $this->bar = $bar; } public function doSomething() { $this->bar->doSomething(); echo __METHOD__; } } $foo = new Foo(new Bar(new Bim())); $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
缺點:若是依賴過多,那麼在構造方法裏必然傳入多個參數,三個以上就會使代碼變的難以閱讀。
相比構造函數注入,setter注入顯得有些複雜,使用也不常見。具體思路是先定義一個接口,包含一個設置依賴的方法。而後依賴類,繼承並實現這個接口。
/** * 接口 */ 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
缺點:一樣存在和第二種方案同樣的弊端,當依賴的類增多時,咱們須要些不少不少的set方法。
基於以上的缺點,這時咱們在想若是有一個專門的類(或者說一個容器)能夠幫咱們管理這些依賴關係就行了。
對於大型項目來講,相互依賴的組件比較多。若是還用手動的方式,本身來建立和注入依賴的話,顯然效率很低,並且每每還會出現不可控的場面。正因如此,IoC容器誕生了。IoC容器其實是一個DI框架,它能簡化咱們的工做量。它包含如下幾個功能:
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();
這個組件如今能夠很簡單的獲取到它所須要的服務,服務採用延遲加載的方式(由於註冊的時候,都是傳入了一個匿名函數,只有在須要使用的時候才初始化),這也節省了服務器資源。這個組件如今是高度解耦。例如,咱們能夠替換掉建立鏈接的方式,它們的行爲或它們的任何其餘方面,也不會影響該組件。
原則&模式|理解DIP、IoC、DI以及IoC容器
PHP程序員如何理解IoC/DI
PHP程序員如何理解依賴注入容器(dependency injection container)
Laravel 依賴注入思想
php依賴注入