控制反转是一种思想,而依赖注入是实现控制反转的一种方式
很多想表述的内容基本都写在注释里了,我觉得这样阅读比较方便,针对代码来讲才是核心,而不是整一堆名词,抛一些模棱两可的概念,弄一些华而不实的东西。以下代码我自己review过2遍,我自认为稍微有一点基础的都可以看懂。
<?php
/**
* 就用发邮件的例子来举例子
* 首先写一个邮件发送者的接口
* 接口里面有个发送方法 send()
* Interface EmailSenderInterface
*/
interface EmailSenderInterface
{
public function send();
}
/**
* 谷歌发送者类实现了邮件发送者的接口
* Class: GmailSender
* Date: 2021/5/18
*/
class GmailSender implements EmailSenderInterface{
private static $instance = null;
/**
* 单例实现
* Date: 2021/5/18
* @return GmailSender|null
*/
public static function getInstance()
{
if (self::$instance == null) {
return self::$instance = new self();
}
return self::$instance;
}
/**
* Override
* 接口中的方法必须被实现(重写)
* Date: 2021/5/18
* @return string
*/
public function send()
{
return '这是来自谷歌发送的验证码:123456';
}
}
/**
* 假设我这里是一个发送邮件验证码的类
* Class: SendEmail
* Date: 2021/5/18
*/
class SendEmail
{
private $_emailSender;
/**
* 这里是依赖注入这个概念的核心,
* 当new SendEmail();这个类的时候会调用__construct()方法,
* 所以当我使用这个类的时候,我就在这个构造方法里去写我需要的其他类,
* 或者其他配置不就好了?是的,然后定义一个属性,然后进行赋值。这样,
* SendEmail类中的 $_emailSender 属性便变成了一个谷歌邮箱发送者的实例。
* SendEmail constructor.
*/
public function __construct()
{
//这是谷歌邮箱
$this->_emailSender = GmailSender::getInstance();
}
/**
* 发送邮箱验证码
* Date: 2021/5/18
* @return string
*/
public function sendCode()
{
/**
* $this->_emailSender 相当于 GmailSender::getInstance();
* return $this->_emailSender->send();等价于return GmailSender::getInstance()->send();
*/
return $this->_emailSender->send();
}
}
$sendEmail = new SendEmail();
print_r($sendEmail->sendCode());
邮件发送成功了。
测试通过,上线了。
好产品来了,谷歌邮箱不太好,用户反应邮件不是延时就是收不到,经过考虑决定新增个QQ邮箱。
新增
/**
* 同样的集成邮件发送接口
* Class: QQSender
* Date: 2021/5/18
*/
class QQSender implements EmailSenderInterface{
private static $instance = null;
public static function getInstance()
{
if (self::$instance == null) {
return self::$instance = new self();
}
return self::$instance;
}
/**
* Override
* 接口中的方法必须被实现(重写)
* Date: 2021/5/18
* @return string
*/
public function send()
{
return '这是来自QQ发送的验证码:654321';
}
}
然后去扩充SendEmail类中的构造方法新增一个QQ邮箱的依赖:
例如这样(错误示范)
class SendEmail
{
private $_emailSender;
//新增一个qq邮箱私有属性
private $_emailQQSender;
public function __construct()
{
//这是谷歌邮箱
$this->_emailSender = GmailSender::getInstance();
//这是新增的qq邮箱
$this->_emailQQSender = QQSender::getInstance();
}
/**
* 发送谷歌邮箱验证码
* Date: 2021/5/18
* @return string
*/
public function sendGmailCode()
{
return $this->_emailSender->send();
}
/**
* 发送qq邮箱验证码
* Date: 2021/5/18
* @return string
*/
public function sendQQCode()
{
return $this->_emailQQSender->send();
}
}
$sendEmail = new SendEmail();
print_r($sendEmail->sendQQCode());
echo PHP_EOL;
print_r($sendEmail->sendGmailCode());
die;
好功能实现了。
测试->上线,产品又双叒叕来了,这次说得批量添加邮箱,要支持所有市面上的邮箱。这可怎么办,然后核心概念主角就出来了
依赖注入,抛出概念之前,可以先想一下,我们要解决什么问题?
要解决的是如何降低耦合,如何变成的可以在外边就可以控制用哪个邮箱发送邮件,而不是用每次去修改构造方法,重新创建个发送方法。
首先看一下现有的类的结构,有两个邮箱属性和2个发送方法。
class SendEmail
{
private $_emailSender;
//新增一个qq邮箱私有属性
private $_emailQQSender;
public function __construct()
{
//这是谷歌邮箱
$this->_emailSender = GmailSender::getInstance();
//这是新增的qq邮箱
$this->_emailQQSender = QQSender::getInstance();
}
/**
* 发送谷歌邮箱验证码
* Date: 2021/5/18
* @return string
*/
public function sendGmailCode()
{
return $this->_emailSender->send();
}
/**
* 发送qq邮箱验证码
* Date: 2021/5/18
* @return string
*/
public function sendQQCode()
{
return $this->_emailQQSender->send();
}
}
调用时候是这样的
$sendEmail = new SendEmail();
//发送qq邮箱验证码
print_r($sendEmail->sendQQCode());
echo PHP_EOL;
//发送谷歌邮箱验证码
print_r($sendEmail->sendGmailCode());
die;
不难发现,每次去新增邮箱都需要重新扩展方法,都需要重新修改构造方法,那能不能不修改呢?不扩展呢?
观察一下构造函数
public function __construct()
{
//这是谷歌邮箱
$this->_emailSender = GmailSender::getInstance();
//这是新增的qq邮箱
$this->_emailQQSender = QQSender::getInstance();
}
有没有发现构造函数做的事情其实是一件事情,就是把实例化后的对象赋值给当前类的属性。
然后优化一下
class SendEmail
{
private $_emailSender;
/**
* 在构造函数值添加形参,因为要实现在外面传入所以构造方法是类的唯一入参方式。
* SendEmail constructor.
* @param $emailSender 这是外面传入进来的实例
*/
public function __construct($emailSender)
{
//将传入进来的实例赋值给本类的私有属性上
$this->_emailSender = $emailSender;
}
/**
* 调用传入进来的实例的send方法
* Date: 2021/5/18
* @return string
*/
public function sendCode()
{
//return $this->_emailSender->send();
//实际等价于:
//第一步:$this->_emailSender = 构造函数传进来的实例
//第二步:调用实例中的send()方法;
return $this->_emailSender->send();
}
}
然后调用一下
$qq = new SendEmail(QQSender::getInstance());
print_r($qq->sendCode());
echo PHP_EOL;
$gmail = new SendEmail(GmailSender::getInstance());
print_r($gmail->sendCode());
die;
不难发现,现在的类已经不需要修改了,只需要传一个参数就可以了,在邮件发送类的外部,注入邮箱的类型,就完成了多邮箱的适配发送。邮箱发送类的控制权,也交给了调用者(控制反转)。
总结:依赖注入,最终的实现思想就是就是降低耦合,把内部依赖变成以注入的方式去实现。换句话说不在类内部使用new来创建依赖对象,而是将其在外部创建好后,通过构造函数或者赋值函数注入进类中,也就是当参数传进去。这样做的同时,控制权也发生了转变,即本来控制权在邮箱发送类的手里,现在缺交给了调用者调用时传入的参数来决定,这个过程其实就叫控制反转(IOC)与依赖注入(DI)