本文章转载自: https://segmentfault.com/a/1190000005602011


最近在研究php的lumen框架和phalcon框架,这两个框架的底层架构都用到了IoC,DI,关于这两个概念自己一直没理解更清晰,找到一篇写得非常好的博文,在此做个备份记录。

基本概念


依赖倒置原则(DIP)(Dependency Inversion Principle):一种软件架构设计的原则(抽象概念)。

控制反转(IoC)(Inversion of Control):一种反转流、依赖和接口的方式(DIP的具体实现方式)。

依赖注入(DI)(Dependency Injection):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。

IoC容器 :依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。

依赖倒置原则


依赖倒置原则,它转换了依赖,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。通俗的讲,就是高层模块定义接口,低层模块负责实现。

Bob Martins对DIP的定义:

高层模块不应依赖于低层模块,两者应该依赖于抽象。

抽象不应该依赖于实现,实现应该依赖于抽象。

场景一 依赖无倒置(低层模块定义接口,高层模块负责实现)



从上图中,我们发现高层模块的类依赖于低层模块的接口。因此,低层模块需要考虑到所有的接口。如果有新的低层模块类出现时,高层模块需要修改代码,来实现新的低层模块的接口。这样,就破坏了开放封闭原则。

场景二 依赖倒置(高层模块定义接口,低层模块负责实现)

在这个图中,我们发现高层模块定义了接口,将不再直接依赖于低层模块,低层模块负责实现高层模块定义的接口。这样,当有新的低层模块实现时,不需要修改高层模块的代码。

由此,我们可以总结出使用DIP的优点:

系统更柔韧:可以修改一部分代码而不影响其他模块。

系统更健壮:可以修改一部分代码而不会让系统崩溃。

系统更高效:组件松耦合,且可复用,提高开发效率。

控制反转 (IoC)


DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IoC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。

软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。一些常见的原则,比如DRY、OCP、DIP等。

软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等。

IoC有2种常见的实现方式:依赖注入和服务定位。主要的实现方式依赖注入。

依赖注入 (DI)


控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。

依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。

php中传递依赖的方法

方法一 直接在高层模块new 底层模块的类

 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注入

相比构造函数注入,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容器诞生了。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依赖注入

深入理解IoC和DI的更多相关文章

  1. 初步理解IOC和DI和AOP模式

    初步理解IOC和DI和AOP模式 控制反转(IOC) 控制反转(IOC,Inversion of Control)是一种转主动为被动关系的一种编程模式,有点类似于工厂模式,举个栗子, 下面这个这不是I ...

  2. 由浅入深理解 IOC 和 DI

    目录 由浅入深理解 IOC 和 DI 开闭原则 OCP(Open Closed Principle) 面向抽象编程 逐步理解实现 IOC 和 DI 的过程(LOL Demo 示例) 比较尴尬的编写程序 ...

  3. 简单理解IoC与DI

    为了理解Spring的IoC与DI从网上查了很多资料,作为初学者,下面的描述应该是最详细,最易理解的方式了. 首先想说说IoC(Inversion of Control,控制倒转).这是spring的 ...

  4. 深入理解 IoC、DI

    本文转载自博客:https://www.cnblogs.com/xinhuaxuan/p/6132372.html 1.控制反转:谁控制谁?控制什么?为何叫反转(对应于正向)?哪些方面反转了?为何需要 ...

  5. 家庭-养老院模型理解IOC和DI

    控制反转 IOC 1. 概念 应用内部不负责依赖对象的创建和维护, 由第三方负责, 这样控制权就由应用内部转移到外部容器, 控制权的转移就是所谓的反转. 2. 比喻 有一户家庭(应用)有个老人(依赖对 ...

  6. 浅谈ASP.NET Core中IOC与DI的理解和使用

    说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...

  7. .net core 2.0学习记录(三):内置IOC与DI的使用

    本篇的话介绍下IOC和ID的含义以及如何使用.Net Core中的DI. 一.我是这么理解IOC和DI的: IOC:没有用IOC之前是直接new实例来赋值,使用IOC之后是通过在运行的时候根据配置来实 ...

  8. 深入理解DIP、IoC、DI以及IoC容器

    摘要 面向对象设计(OOD)有助于我们开发出高性能.易扩展以及易复用的程序.其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC.DI以及Ioc容器等概念.通过本文我们将一起学 ...

  9. 深入理解IoC/DI

    ------------------------------------------------------------------------ 理解IoC/DI 1.控制反转 --> 谁控制谁 ...

随机推荐

  1. WCF入门教程(四)通过Host代码方式来承载服务 一个WCF使用TCP协议进行通协的例子 jquery ajax调用WCF,采用System.ServiceModel.WebHttpBinding System.ServiceModel.WSHttpBinding协议 学习WCF笔记之二 无废话WCF入门教程一[什么是WCF]

    WCF入门教程(四)通过Host代码方式来承载服务 Posted on 2014-05-15 13:03 停留的风 阅读(7681) 评论(0) 编辑 收藏 WCF入门教程(四)通过Host代码方式来 ...

  2. PHOTOSHOP中3D下拉菜单为灰色如何设置

    方法/步骤   安装好PS后,在测试3D功能时突然发不能用.如图,怎么办呢?   按“CTRL+K”打开,或者在编辑-首选项-性能-勾选“启用OpenGL绘图(D)”   在选项对话框中勾选“启用Op ...

  3. 【转】Tesla Model X的车门设计问题

    Tesla Model X的车门设计问题 Tesla即将推出的SUV(Model X),不但继承了以上提到的Model S的各种问题(触摸屏,门把,……),而且还制造了新的问题.Model X具有一个 ...

  4. jquery中页面Ajax方法$.load的功能

    load语法 $(".selector").load("url", function(responseText, statusText, xmlhttprequ ...

  5. 向量运算 与 JavaScript

    二维向量都包含两个值:方向(direction)及大小(magnitude)   这两个值可以表达出各种各样的物理特性来,比如力和运动.如两个物体间的碰撞检测.   向量的大小   虽说二维向量是对大 ...

  6. C#基础第四天-作业答案-Hashtable-list<KeyValuePair>泛型实现名片

    .Hashtable 实现 Hashtable table = new Hashtable(); while (true) { Console.WriteLine("------------ ...

  7. flowable Service介绍

    1 原始数据库表说明 ACT_RE_*: RE stands for repository. Tables with this prefix contain static information su ...

  8. springboot 多模块 maven 项目构建jar 文件配置

    最近在写 springboot 项目时,需要使用多模块,遇到了许多问题. 1 如果程序使用了 java8 的一些特性,springboot 默认构建工具不支持.需要修改配置 ... </buil ...

  9. 人民币金额转化汉字的java写法

    最近看到一个把一个浮点数转化为汉字人民币的小题,感觉很有意思就去用java实现了一下,没想到没有想得那么简单,在网上搜了一下也不近人意,经过几次修改后,现在实现了,现在分享一下. 一.当输入一个浮点数 ...

  10. Android SDK不能够更新

    Adroid不能够更新,因为国内将google的服务器墙掉了,在 1) vim /etc/hosts(Windows上路径为:C:\Windows\System32\drivers\etc\hosts ...