@原文urlhttp://www.digpage.com/di.html
  https://my.oschina.net/cxz001/blog/227482?fromerr=eQovcbw9

  理解什么是Di/IoC,依赖注入/控制反转。两者说的是一个东西,是当下流行的一种设计模式。大致的意思就是,准备一个盒子(容器),事先将项目中可能用到的类扔进去,在项目中直接从容器中拿,也就是避免了直接在项目中到处new,造成大量耦合。取而代之的是在项目类里面增设 setDi()和getDi()方法,通过Di同一管理类。

  依赖注入(Denpdency Injection, DI)依赖注入容器(DI容器)
  为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Service Locator)两种模式。这些设计模式对于提高自身的设计水平很有帮助,这也是我们学习Yii的一个重要出发点。

  相关概念

  依赖倒置原则(Dependence Inversion Principle, DIP)
  DIP是一种软件设计的指导思想。传统软件设计中,上层代码依赖于下层代码,当下层出现变动时, 上层代码也要相应变化,维护成本较高。而DIP的核心思想是上层定义接口,下层实现这个接口, 从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。这是一种经实践证明的有效策略。

  控制反转(Inversion of Control, IoC)
  IoC就是DIP的一种具体思路,DIP只是一种理念、思想,而IoC是一种实现DIP的方法。 IoC的核心是将类(上层)所依赖的单元(下层)的实例化
过程交由第三方来实现。 一个简单的特征,就是类中不对所依赖的单元有诸如 $component = new yii\component\SomeClass() 的实例化语句。

  依赖注入(Dependence Injection, DI)
  DI是IoC的一种设计模式,是一种套路,按照DI的套路,就可以实现IoC,就能符合DIP原则。 DI的核心是把类所依赖的单元的实例化过程,放到类的外面去实现。
        
  控制反转容器(IoC Container)
  当项目比较大时,依赖关系可能会很复杂。 而IoC Container提供了动态地创建、注入依赖单元,映射依赖关系等功能,减少了许多代码量。 Yii 设计了
一个 yii\di\Container 来实现了 DI Container。

  服务定位器(Service Locator)
  Service Locator是IoC的另一种实现方式, 其核心是把所有可能用到的依赖单元交由Service Locator进行实例化和创建、配置, 把类对依赖单元的依赖,
转换成类对Service Locator的依赖。 DI 与 Service Locator并不冲突,两者可以结合使用。 目前,Yii2.0把这DI和Service Locator这两个东西结合起来使用,或
者说通过DI容器,实现了Service Locator。

  

  一,依赖注入,首先讲讲DI

  在Web应用中,很常见的是使用各种第三方Web Service实现特定的功能,比如发送邮件、推送微博等。 假设要实现当访客在博客上发表评论后,向博文的作者发送Email的功能,通常代码会是这样:

// 为邮件服务定义抽象层
interface EmailSenderInterface
{
    public function send(...);
}

// 定义Gmail邮件服务
class GmailSender implements EmailSenderInterface
{
    ...

    // 实现发送邮件的类方法
    public function send(...)
    {
        ...
    }
}

// 定义评论类
class Comment extend yii\db\ActiveRecord
{
    // 用于引用发送邮件的库
    private $_eMailSender;

    // 初始化时,实例化 $_eMailSender
    public function init()
    {
        ...
        // 这里假设使用Gmail的邮件服务
       $this->_eMailSender = GmailSender::getInstance();
        ...
    }

    // 当有新的评价,即 save() 方法被调用之后中,会触发以下方法
    public function afterInsert()
    {
        ...
        //
        $this->_eMailSender->send(...);
        ...
    }
}

 

  上面的代码只是一个示意,大致是这么个流程。

  那么这种常见的设计方法有什么问题呢? 主要问题在于 Comment 对于 GmailSender 的依赖(对于EmailSenderInterface的依赖不可避免), 假设有一天突然不使用Gmail提供的服务了,改用Yahoo或自建的邮件服务了。 那么,你不得不修改 Comment::init() 里面对 $_eMailSender 的实例化语句:

  $this->_eMailSender = MyEmailSender::getInstance();

  这个问题的本质在于,你今天写完这个Comment,只能用于这个项目,哪天你开发别的项目要实现类似的功能, 你还要针对新项目使用的邮件服务修改这个Comment。代码的复用性不高呀。 有什么办法可以不改变Comment的代码,就能扩展成对各种邮件服务都支持么? 换句话说,有办法将Comment和GmailSender解耦么?有办法提高Comment的普适性、复用性么?

  依赖注入就是为了解决这个问题而生的,当然,DI也不是唯一解决问题的办法,毕竟条条大路通罗马。 Service Locator也是可以实现解耦的。

  在Yii中使用DI解耦,有2种注入方式:构造函数注入、属性注入。

  构造函数注入

  构造函数注入通过构造函数的形参,为类内部的抽象单元提供实例化。 具体的构造函数调用代码,由外部代码决定。具体例子如下:

// 这是构造函数注入的例子
class Comment extend yii\db\ActiveRecord
{
    // 用于引用发送邮件的库
    private $_eMailSender;

    // 构造函数注入
    public function __construct($emailSender)
    {
        ...
        $this->_eMailSender = $emailSender;
        ...
    }

    // 当有新的评价,即 save() 方法被调用之后中,会触发以下方法
    public function afterInsert()
    {
        ...
        //
        $this->_eMailSender->send(...);
        ...
    }
}

// 实例化两种不同的邮件服务,当然,他们都实现了EmailSenderInterface
sender1 = new GmailSender();
sender2 = new MyEmailSender();

// 用构造函数将GmailSender注入
$comment1 = new Comment(sender1);
// 使用Gmail发送邮件
$comment1.save();

// 用构造函数将MyEmailSender注入
$comment2 = new Comment(sender2);
// 使用MyEmailSender发送邮件
$comment2.save();

  上面的代码对比原来的代码,解决了Comment类对于GmailSender等具体类的依赖,通过构造函数,将相应的实现了 EmailSenderInterface接口的类实例传入Comment类中,使得Comment类可以适用于不同的邮件服务。 从此以后,无论要使用何何种邮件服务,只需写出新的EmailSenderInterface实现即可, Comment类的代码不再需要作任何更改,多爽的一件事,扩展起来、测试起来都省心省力

  属性注入

  与构造函数注入类似,属性注入通过setter或public成员变量,将所依赖的单元注入到类内部。 具体的属性写入,由外部代码决定。具体例子如下:

// 这是属性注入的例子
class Comment extend yii\db\ActiveRecord
{
    // 用于引用发送邮件的库
    private $_eMailSender;

    // 定义了一个 setter()
    public function setEmailSender($value)
    {
        $this->_eMailSender = $value;
    }

    // 当有新的评价,即 save() 方法被调用之后中,会触发以下方法
    public function afterInsert()
    {
        ...
        //
        $this->_eMailSender->send(...);
        ...
    }
}

// 实例化两种不同的邮件服务,当然,他们都实现了EmailSenderInterface
sender1 = new GmailSender();
sender2 = new MyEmailSender();

$comment1 = new Comment;
// 使用属性注入
$comment1->eMailSender = sender1;
// 使用Gmail发送邮件
$comment1.save();

$comment2 = new Comment;
// 使用属性注入
$comment2->eMailSender = sender2;
// 使用MyEmailSender发送邮件
$comment2.save();

  上面的Comment如果将 private $_eMailSender 改成 public $eMailSender 并删除 setter函数, 也是可以达到同样的效果的。

  与构造函数注入类似,属性注入也是将Comment类所依赖的EmailSenderInterface的实例化过程放在Comment类以外。 这就是依赖注入的本质所在。为什么称为注入?从外面把东西打进去,就是注入。什么是外,什么是内? 要解除依赖的类内部就是内实例化所依赖单元的地方就是外

  

  二,依赖注入容器,下面讲DI容器(以Yii Di容器为案例)

  ---蛋定龙php系列视屏教程-Yii2依赖注入容器

  从上面DI两种注入方式来看,依赖单元的实例化代码是一个重复、繁琐的过程。 yii中一个Web应用的某一组件会依赖于若干单元,这些单元又有可能依赖于更低层级的单元, 从而形成依赖嵌套的情形。那么,这些依赖单元的实例化、注入过程的代码可能会比较长,前后关系也需要特别地注意, 必须将被依赖的类放在需要注入依赖的代码前面进行实例化。 这实在是一件既没技术含量,又吃力不出成果的工作,这类工作是高智商(懒)人群的天敌, 我们是不会去做这么无聊的事情的。怎么办?使用DI容器

 ,准备一个盒子(容器),事先将项目中可能用到的类扔进去,在项目中直接从容器中拿,也就是避免了直接在项目中到处new,造成大量的耦合,取而代之的是在项目类里面增设setDi()和getDi()方法,通过Di统一管理类。

  

  ---Yii容器提供依赖注入类型

  

  ---Yii Di容器主要函数

  

  ---set和setSingleton的区别

  

  (一),视频相关代码

1,yii中的回调注入,例如下边代码:
public function actionCallbackdi(){
    $container = new Container();    //声明一个yii-Di容器对象
    //注册对象
    $container -> set('Foo', function($container,$params,$config){
        echo "<pre>";
        $obj new Foo();
        //这里很重要,设置依赖,$obj->Bar = get获取另外一个实例对象。
        $obj -> Bar = $container -> get('dandinglong\containertest\Bar');
        return $obj;
    });
}
---类似于di-demo中的匿名函数注入,把$container对象当做函数参数注入,然后返回想要的对象。

2,Yii2中错误使用Di容器01---找不到类
public function actionErrordione(){
    $container = new Container();
    $aaa = $container->get('abc');    //没有set abc这个类,所以这里获取不到,会报Class abc does not exist
    var_dump($aaa);
}

 

@总结(不用Di容器,用什么,读Spring有感?):

  ***可以参照di-demo案例或者yii案例来了解Di容器的使用

  ***阅读Struts2+Spring3+Hibemate整合开发,Spring对于Di容器的理解?
  1,如果开发中不用Di容器,应用中各组件以怎样的方式耦合?
    很多经验丰富的项目经理统一回答,用工厂模式,服务定位器模式等。(实现了部分spring功能)
  2,,spring的核心机制:依赖注入(目前最优秀的解耦方式)
  3,依赖注入,控制反转,其含义完全相同,依赖注入是因为控制反转太难理解,才重新起的名字。
    Ioc容器=Di容器
  4,所谓依赖注入,是指程序运行过程中,如果需要另一个对象协作(调用它的方法,访问它的属性)时,无须在代码中创建
被调用者,而是依赖于外部容器的注入。
  5,编码的三种请求,三种境界
    (1),第一种情况,java实例的调用者创建被调用的Java实例,调用者直接使用new创建被调用者实例,程序高度耦合,效率低下
    ,真实的应用及少使用这种方式。
    (2),第二种情况,调用者无须关心被调用者的具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的
    代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要主动定位工厂,调用者与
    工厂耦合在一起。
    (3),第三种情况,最理想的情况:程序完全无须理会被调用者的实现,也无须主动定位工厂,这是最好的解耦方式。实例之间
    依赖关系由Ioc容器负责管理。

posted on 2017-01-09 21:03  学到老死  阅读(474)  评论(0)    收藏  举报