Yii2中的 依赖注入

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

// 为邮件服务定义抽象层
interface EmailSenderInterface{
  public function send(){}
}
// 定义Gmail邮件服务
class GmailSender implments 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的普适性、复用性么?

依赖注入就是为了解决这个问题而生的!

构造函数注入

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

// 这是构造函数注入的例子
class Comment extend yii\db\ActiveRecord{
  // 用于引用发送邮件的库
  private $_eMailSender;
  // 构造函数注入
  public function __construct($emailSender){
    $this->_eMailSender = $emailSender;
  }
  // 当有新的评论,即save()方法被调用之后,会触发以下方法
  public function afterInsert(){
    $this->_eMailSender->send();
  }
}

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

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

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

上面的代码对比原来的代码,解决了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;
  }
  public function afterInsert(){
    $this->_eMailSender->send();
  }
}

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

$comment1 = new Comment();
$comment1->emailSender = sender1; //使用属性注入
$comment1.save(); 


$comment2 = new Comment();
$comment2->emailSender = sender2;//使用属性注入
$comment2.save(); 

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值