本文章转载自: 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依赖注入