依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式。
这句解释让依赖注入的概念听起来比它实际要复杂很多。依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。就是这么简单。
基本概念
我们可以用一个简单的例子来说明依赖注入的概念。
下面的代码中有一个 Shop 的类,它需要一个Mysql类来与数据库交互。我们在构造函数里实例化了Mysql,从而产生了耦合。这会使测试变得很困难,而且 Shop类和Mysql类耦合的很紧密。
<?php namespace app\services; class Mysql { public function connect(): string { return "this is mysql link"; } }
<?php
namespace app\services;
class Shop
{
protected $db;
public function __construct()
{
$this->db = new Mysql();
}
public function connect(): string
{
return $this->db->connect();
}
}
$shop = new Shop(); echo $shop->connect();
在这种情况下,我们使用new 的方式在内部进行,Shop类严重依赖于Mysql类。每当Mysql类变化时,Product类也得进行变化。
依赖注入
我们可以用依赖注入重构代码,从而实现解耦
<?php namespace app\services; class Mysql { public function connect(): string { return "this is mysql link"; } }
<?php
namespace app\services;
class Shop
{
protected $db;
public function __construct(Mysql $db)
{
$this->db = $db;
}
public function connect(): string
{
return $this->db->connect();
}
}
$shop = new Shop(new Mysql()); echo $shop->connect();
我们对程序进行了解耦。目前的话,Mysql类无论如何变动,Shop类是不需要变动的,不再依赖于Mysql类。
新问题:假如后期我们把数据库换成了SqlServer,那么这时候Shop类还是得改动。
依赖反转准则
依赖反转准则是面向对象设计准则 S.O.L.I.D 中的 “D” ,倡导 “依赖于抽象而不是具体”。简单来说就是依赖应该是接口或者抽象类,而不是具体的实现。我们能很容易重构前面的代码,使之遵循这个准则
<?php
namespace app\services;
interface Database
{
public function connect();
}
<?php
namespace app\services;
class Mysql implements Database
{
public function connect(): string
{
return "this is mysql link";
}
}
<?php
namespace app\services;
class Shop
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function connect(): string
{
return $this->db->connect();
}
}
$shop = new Shop(new Mysql()); echo $shop->connect();
现在 Shop类依赖于接口,后期我们可以随意将数据库换成SqlServer,Oracle,而不需要修改Shop类。
新问题:我们目前只是一个简单的示例,Shop类当只要实例化一个Mysql类,但是现实当中,你的依赖会逐渐增多。例如A依赖B,B依赖C,C依赖D,层层依赖,那么相当于我们要实例化A类的话,需要编写以下代码:
$a=new A(new B(new C(new D)))
也就是不论我们的依赖有多少层,如何自动地去引入相应地依赖呢?那么需要用到容器了。
依赖注入容器
你应该明白的第一件事是依赖注入容器和依赖注入不是相同的概念。依赖注入容器是帮助我们更方便地实现依赖注入的工具。
下面我们PHP-DI来优化一下代码
<?php namespace app\services; interface Database { public function connect(); }
<?php
namespace app\services;
class Mysql implements Database
{
public function connect(): string
{
return "this is mysql link";
}
}
<?php
namespace app\services;
class Shop
{
protected $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function connect(): string
{
return $this->db->connect();
}
}
$container = new \DI\Container();
$container->set(Database::class, \DI\create(Mysql::class));
$shop = $container->get(Shop::class);
echo $shop->connect();
我们可以看到PHP-DI会自动解决依赖关系, 将依赖实例化并注入新创建的对象。依赖关系的解决是递归的, 如果一个依赖关系中还有其他依赖关系, 则这些依赖关系都会被自动解决。
容器的具体实现是用了PHP的反射。大家可以多看看反射的参考文档。
参考链接:
https://laravel-china.github.io/php-the-right-way/#further_reading
《深入PHP面向对象,模式与实践》(第5版)