php 如何实现依赖注入,谈谈PHP实现依赖注入(控制反转)

本文探讨了依赖注入的概念,通过实例展示了如何通过工厂模式解决类之间的依赖问题。进一步引入了依赖注入容器,解释了如何利用容器进行类的管理和依赖解析,以提高代码的灵活性和可维护性。同时,提到了接口的重要性,确保了类之间的松耦合。文章还提及了PHP中的依赖注入实现,如Symfony的DependencyInjection组件和Laravel的服务容器。
摘要由CSDN通过智能技术生成

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

假设我们有两个类: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手册中查阅服务容器相关的章节,有兴趣可以研究他们的代码,核心是借助反射。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值