浅谈PHP实现依赖注入(控制反转)
推荐

浅谈PHP实现依赖注入(控制反转)
依赖注入,听起来十分高大上,貌似是从java传出来的,不少PHP框架也已经使用了这种模式,那么就来看看它到底是什么吧!

首先我们抛开那玄乎其神的定义,我们先从一个场景入手。

假设我们有两个类:class A 和 class B,以下简称A和B;

现在我们要使用A,而A依赖了B,A的构造如下:

class A {
    private $b;
    public function __construct()     {
         $this->b = new B;   
    }
}

我们知道这种写法十分不利于后期维护,一旦B发生了变化,A不得不修改。

解决这种依赖有很多种方式,但是这里只提一种:

class A {
    private $b;
    public function setB($b)     {
        $this->b = $b;
    }
}

现在至少A的内部没有关于B的代码了,我们将依赖转到外部去,现在创建A实例需要这样:

$a = new A;
$a->setB(new B);

这样写多不方便啊,于是我们再添加一个工厂类,专门封装这样的逻辑

class Factory {
    public static function getA()     {
        $a = new A;
        $a->setB(new B);
        return $a;
    }
}

$a = Factory::getA();

这样通过一个工厂来维护类的产生,统一管理,方便维护。

然而 A::setB() 方法参数并没有限制类型,也就是可以任意传入一个参数,这显然缺少约束,所以需要接口,接口一般是事先设计好的约定,不会变更,这里我们假设设定一个接口

interface BInterface{}

B需要实现接口

class B implements BInterface{}

改造一下A

class A {
    private $b;
    public function __construct(BInterface $b)     {
         $this->b = $b;   
    }
}

工厂

class Factory {
    public static function getA()     {
        $a = new A(new B);
        return $a;
    }
}

$a = Factory::getA();

前面说了,接口是不会随意变化的,所以我们可以放心的在构造函数中要求传入b,不管B是什么具体实现,只要符合接口就不会影响A。

这里为什么还要用工厂呢?有人说我直接在业务代码里 new A 就可以了啊,仔细想想,A本身如果发生变化了呢?哪天构造函数变成了

function __construct(C $c,B $b){}

所以工厂统一了实例化过程,一旦A变化,只要改工厂类即可。

那么,工厂类本身体积越来越大怎么办?同样难以维护!好,接下来就是见证奇迹的时刻,哦不,是掀开依赖注入面纱的时刻。

现在我们假设,有没有一个万能的工厂方法,可以帮我们生产不同的类,并自动解决依赖问题?

现在我们创建一个类来模拟这个功能

class Di {
    function make($class){}
}

对应的使用过程

$di = new Di; $a = $di->make(A::class);//参数是php5.5的语法,可以获取一个类的名字,不容易出错,传统的可以用字符串'A'替代 $a->getB();//获取 A::$b 属性

ok,这就完啦,我们不用关系它内部怎么处理的,我只知道我拿到了一个A的对象啦,并且跟上面的工厂方法一样,已经帮我传入了B对象,更惊奇的是,这个方法并不仅限于A类,可以对B、C、D、……任意的类使用。

$c = $di->make(C::class); $d = $di->make(D::class); //...

这个特性叫依赖注入,di可以自动分析目标类的依赖,并从其他地方解决这些依赖,这时候为了方便管理,引出了一个容器的概念,我们事先往容器中注册一些类或对象,分别有个别名,这样每次make就不需要知道具体类的名字,又一次解耦。

比如我想获得一个redis的实例,我不用关心这个redis具体是哪个类,我只要告诉di我想要名为'redis'的对象。

$di->bind('redis',MyRedis::class);//事先注册redis服务 $redis = $di->make('redis');
$redis->get('key');//可以使用redis对象了

di会自动帮我们实例化 MyRedis,假设MyRedis依赖类B接口的某个类

class MyRedis {
    public function __construct(BInterface $b){}
}

那么这时候di也能从容器中查找实现这个接口的类,并注入到MyRedis中,是不是很强大?

$di->bind('BInterface',B::class); $di->bind('redis',MyRedis::class); $di->make('redis');//相当于 new MyRedis($di->make('BInterface'))

这样就很灵活了,依赖的那个类也可能依赖别的类,而只要把一切类放在容器中,由di自动去解决依赖,我们只管 bind 和 make 就好了,不用写那么多工厂方法。

而类本身也不会依赖di,di我觉得就是一个全局的依赖管理器和工厂的结合。

至于PHP的di有哪些实现,太多了,你只要在github上搜 "php di",或者直接去symfony网上找一个叫做"DependencyInjection"的组件,或者去laravel手册中查阅服务容器相关的章节,有兴趣可以研究他们的代码,核心是借助反射。

我描述的内容比较简单,事实上 依赖注入/控制反转/服务容器 是一个东西罢了,思路也很清晰,只是包装的复杂程度不同罢了。

版权属于:够过瘾——挨踢男的葵花宝典

文章标签:iocdi

文章链接:http://www.gouguoyin.cn/php/137.html

转载时必须以链接形式注明原始出处及本声明。

如果您觉得本文对您有所帮助,请小额赞助一下,我会优先回答您在使用过程中出现的问题,点此赞助

如有疑问或遇到技术问题,请加官方QQ群: 421537504   GoCMS官方交流群

文章点评:

表情