简介
依赖注入 Dependency Injection 简称 DI
,目的是让代码耦合度降低,模块化程度高
,让代码更易测试
什么是依赖
为什么会有依赖?因为我们为了模块化,把各种小功能都做成了一个模块,模块之间相互调用,这样就产生了依赖。
耦合
一个好的代码结构设计一定是松耦合的,这也是很多通用设计模式的宗旨,就是把分散在各处的同一个功能的代码汇聚到一起,形成一个模块,然后在不同模块之间通过一些细小的、明确的渠道进行沟通。
在实践中,不同功能和模块之间的互相依赖是不可避免的,而如何处理好这些依赖之间的关系则是代码结构能否变得美好的关键。
没有用依赖注入的情况
传统的思路是应用程序用到一个User 类,就会创建User 类并调用User 类的方法
假如这个方法内需要一个Notify 类,就会创建Notify 类并调用Notify 类的方法,而这个方法内需要一个Email 类,
就会创建Email 类,接着做些其它工作。
//用户类
class User{
public function register($user)
{
echo "用户注册 | ";
// 注册操作
// 发送确认邮件
$notify = new Notify();
$notify->sendEmail('register', $user);
}
}
//通知类
class Notify{
public function sendEmail($type, $data) {
echo "发送通知 | ";
switch ($type) {
case 'register':
// 发送注册确认邮件
$email = new Email($type);
$email->send($data);
}
}
}
//邮箱类
class Email{
public function send($data) {
echo "发送邮件 | ";
// 发送邮件
}
}
$user = new User();
$user->register('用户:黎明强'); // 用户注册 | 发送通知 | 发送邮件 |
上述代码中,三个类之间逐层依赖,三个类实例化的顺序是 User -> Notify -> Email
。
也就是说我先实例化User类,可能执行了一些代码之后再去实例化我需要的其他类,比如Notify,以此类推。
这种依赖会让我们不得不为了得到需要的依赖而去做的一些准备工作,有时候可能一个new操作还不够。
而这部分工作就是所说的耦合,他会让一个独立功能的类不得不去关心一些和自己的主体功能没什么关系的操作。
如何解除一个类对其他类的依赖?
要解决这个问题也很简单,我可以
- 先实例化好Email类,
- 然后再实例化Notify,然后把Email对象作为参数传给Notify
- 最后实例化User类,然后把Notify传进去。
这就是所谓的依赖注入,可以看到这个过程中类实例化的顺序完全反过来了,先实例化被依赖的对象,而不是先实例化最终需要的对象,这是**控制反转
**。
使用依赖注入
可以通过构造函数来注入需要的依赖,也可以用一些其他的方法。
代码如下:
//用户类
class User{
protected $notify;
public function __construct(Notify $notify)
{
$this->notify = $notify;
}
public function register($user)
{
echo "用户注册 | ";
$this->notify->sendEmail('register',$user);
}
}
//通知类
class Notify{
protected $email ;
public function __construct(Email $email)
{
$this->email = $email;
}
public function sendEmail($type, $data) {
echo "发送通知 | ";
switch ($type) {
case 'register':
// 发送注册确认邮件
$this->email->send($data);
}
}
}
//邮箱类
class Email{
public function send($data) {
echo '发送邮件 | ';
// 发送邮件
}
}
//调用-------
$email = new Email();
$notify = new Notify($email);
$user = new User($notify);
$user->register('liming'); // 用户注册 | 发送通知 | 发送邮件 |
使用依赖注入的好处是显而易见的,我们通过参数了让 email对象通过参数传到了 noitfy 类中,而不是在 noitfy 类中的方法中实例化 email 类,从而将 email类和 noitfy 类解耦 。
使用依赖注入容器来管理依赖
那又有新的问题,例子中只有三个类还好,那如果这个User类依赖Notify来发邮件,依赖Model来存数据库,依赖redis来缓存,这样固然把依赖关系转移到了类的外部,但还是会导致我只想实例化一下User的时候,却要手动做很多的准备工作,会让代码混乱。
所以这个时候需要一个容器。而这个容器的作用就是替我来管理这些依赖。
1 、 定义容器类
这段代码使用了魔术方法
,在给不可访问属性赋值时,
- __set() 会被调用。读取不可访问属性的值时,
- __get() 会被调用。
// 容器
class Container
{
private $s = array();
function __set($k, $c)
{
$this->s[$k] = $c;
}
function __get($k)
{
return $this->s[$k]($this);
}
}
在程序启动的时候,我们可以在一个地方统一的注册好一系列的基础服务。
$c = new Container();
$c->email = function () {
return new Email();
};
$c->notify = function ($c) {
return new Notify($c->email);
};
$c->user = function ($c) {
return new User($c->notify);
};
// 从容器中取得user
$foo = $c->user;
$foo->register('liming'); // 用户注册 | 发送通知 | 发送邮件 |
这段代码使用了匿名函数, 总之容器负责实例化,注入依赖,处理依赖关系等工作。
演示 :容器代码
再来一段简单的代码演示一下,容器代码
class IoC
{
protected static $registry = [];
public static function bind($name, Callable $resolver)
{
static::$registry[$name] = $resolver;
}
public static function make($name)
{
if (isset(static::$registry[$name])) {
$resolver = static::$registry[$name];
return $resolver();
}
throw new Exception('Alias does not exist in the IoC registry.');
}
}
IoC::bind('email', function () {
return new Email();
});
IoC::bind('noitfy', function () {
return new Notify(IoC::make('email'));
});
IoC::bind('user', function () {
return new User(IoC::make('noitfy'));
});
echo "<pre>";
// 从容器中取得User
$foo = IoC::make('user');
$foo->register('liming'); // 用户注册 | 发送通知 | 发送邮件 |
这段代码使用了后期静态绑定