Yii2.0 设计模式 - 依赖注入 Dependent Injection

前言:

这是我第二个博文说到DI,IOC,上次说的是spring实现DI的一个方式。今天要说的是Yii2.0 实现的DI,实际上有很多相同之处。在实现解析依赖的时候,都采用了反射的思想。

何为反射?如果把php运行时看做是一面镜子, 把php代码看做是一个物体。当你的程序在php运行时运行的时候, 你的程序想知道你现在运行的php代码的一些信息。 比如说我想动态调用一个类的方法,或者我想知道我的一个类到底有哪些方法,有哪些属性。 这就叫做反射。)

该博文参考 深入理解Yii2.0 http://www.digpage.com/di.html ,写得非常详细清楚,但是我感觉他写的有些枯燥,上来就是概念源码。所以我这里改变一下策略,简单说下概念 之后直接先说例子。

概念

· 依赖倒置原则(Dependence Inversion Principle, DIP)
· 控制反转(Inversion of Control, IoC)
· 依赖注入(Dependence Injection, DI)

先区分以上概念

DIP是一种软件设计的指导思想。
传统软件设计中,上层代码依赖于下层代码,当下层出现变动时, 上层代码也要相应变化,维护成本较高。
DIP的核心思想是上层定义接口,下层实现这个接口, 从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。

IoC也是一种指导思想。是一种实现DIP的方法。
IoC的核心是将类(上层)所依赖的单元(下层)的实例化过程交由第三方来实现。在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

DI是IoC的一种设计模式(套路)。
按照DI的套路,可以实现IoC。DI 和 IoC是同一个概念的不同角度描述,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

先看Yii官方例子

namespace app\models;

use yii\base\Object;
use yii\db\Connection;

// 定义接口
interface UserFinderInterface
{
    function findUser();
}

// 定义类,实现接口
class UserFinder extends Object implements UserFinderInterface
{
    public $db;

    // 从构造函数看,这个类依赖于 Connection
    public function __construct(Connection $db, $config = [])
    {
        $this->db = $db;
        parent::__construct($config);
    }

    public function findUser()
    {
    }
}

class UserLister extends Object
{
    public $finder;

    // 从构造函数看,这个类依赖于 UserFinderInterface接口
    public function __construct(UserFinderInterface $finder, $config = [])
    {
        $this->finder = $finder;
        parent::__construct($config);
    }
}

1.我们看到上面的代码,可以归纳他的依赖关系

UserLister 类依赖于接口 UserFinderInterface , 而接口有一个实现就是 UserFinder 类,但这类又依赖于 Connection。

画个图理解下:

2.然后我们迅速写一下他的实例化

$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);

上面这段代码就是逆着依赖关系来从最底层Connection开始实例化。这个顺序不能改变。

不使用DI的后果:
  1. 这个关系在上面的例子上看起来很简单,但单依赖关系达到10-20个,就会很麻烦了。要特别注意他们的依赖关系,顺序不能搞错了。实例化的代码也会很多。

  2. 团队开发的时候,很多模块是共用的,比如邮件投递服务。这个我之前的项目也遇到过,在修改账号密码,在注册,在后台管理等等都要调用到这个邮件投递的组件,或者说模块。对,我采用的是直接实例化,但如果这不是我自己的项目,是公司的大型项目,如果有10个模块调用到邮件投递服务,难道我要在代码不同地方new 10 次?OK , 假设大家都这么干了,但比如说我这个邮件投递模块是Gmail的,然后有一天Google说他不干了,Gmail下线,然后我们的程序员迅速把Gmail改了然后他把类名修正过来,不叫Gmail了,OK,他要在庞大的代码中找到10处实现了Gmail的代码,把Gmail给改过来。

于是乎,Yii2.0 和spring 都采用了DI的设计模式来解决以上的问题。spring怎么实现的,可以参考我之前的博客。接下来说Yii2.0是怎么实现的。

Yii 2.0 实现DI

还是先说上面的例子的Yii2.0 的解决方法。

use yii\di\Container;

// 创建一个DI容器
$container = new Container;

// 为Connection指定一个数组作为依赖,当需要Connection的实例时,
// 使用这个数组进行创建
$container->set('yii\db\Connection', [
    'dsn' => '...',
]);

// 在需要使用接口 UserFinderInterface 时,采用UserFinder类实现
$container->set('app\models\UserFinderInterface', [
    'class' => 'app\models\UserFinder',
]);

// 为UserLister定义一个别名
$container->set('userLister', 'app\models\UserLister');

// 获取这个UserList的实例
$lister = $container->get('userLister');

先不管这个Container类的源码是怎么实现的。我们就先从一个使用者的角度来理解。

先把set函数归为一起看,set()就是写入特定的数据结构,没有涉及到具体依赖关系的解析。所以,前后关系是不重要的,不需要考虑他们的依赖关系。

然后我们再思考,那怎么知道Container怎么知道它们之间的依赖关系呢。这就是回到了我为什么要在博文一开头介绍反射的思想了。通过反射我们就可以知道类以及他们构造函数中的依赖关系。(这里要用到ReflectionClass::getConstructor, 通过读取构造函数的参数可以知道依赖关系)

上面的代码只有一个get() 看起来好像根本没有实例化其他如 Connection 单元一样,但事实上,DI容器已经安排好了一切。 在获取 userLister 之前, Connection 和 UserFinder 都会被自动实例化。 其中, Connection 是根据依赖定义中的配置数组进行实例化的。

所以一切的精华,一切的秘密都在get(‘userLister’)。
想要了解这个实现的话,就要涉及到源码了。Yii 通过 yii\di\Container 类提供 DI 容器特性。它支持如下几种类型的依赖注入:

构造方法注入;
Setter 和属性注入;
PHP 回调注入.

Yii2.0 DI 源码

未完待续。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值