博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谈谈PHP实现依赖注入(控制反转)
阅读量:5884 次
发布时间:2019-06-19

本文共 2495 字,大约阅读时间需要 8 分钟。

  hot3.png

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

假设我们有两个类: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网上找一个叫做""的组件,或者去laravel手册中查阅服务容器相关的章节,有兴趣可以研究他们的代码,核心是借助反射。

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

转载于:https://my.oschina.net/cxz001/blog/533166

你可能感兴趣的文章
二叉树前序、中序、后序遍历相互求法
查看>>
xcode9 上传app后iTues 构建版本不显示
查看>>
tcpdump http://www.cnblogs.com/daisin/articles/5512957.html
查看>>
win10下nvidia控制面板看不到
查看>>
夏季学期实训-基础数据结构(栈与队列)
查看>>
Oracle索引简单介绍与示例
查看>>
react -- 计时器
查看>>
k8s入门系列之介绍篇
查看>>
The last working day of 2011
查看>>
686. Repeated String Match - Easy
查看>>
codevs——2651 孔子教学——同桌
查看>>
128*64液晶显示器
查看>>
Maven项目
查看>>
B树 B+树 红黑树
查看>>
How to check in Windows if you are using UEFI
查看>>
【LeetCode】Divide Two Integers
查看>>
IOS使用Jenkins进行持续集成
查看>>
fiddler抓包工具
查看>>
git 修改客户端用户名和密码
查看>>
FileStream读写文件【StreamWriter 和 StreamReader】
查看>>