为什么使用
依赖注入,最重要的一点好处就是有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称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的普适性、复用性么?
在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();
利用属性
// 这是属性注入的例子
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();
推荐
https://segmentfault.com/a/1190000007209266 https://segmentfault.com/a/1190000011975467