系列文章---框架篇:8.依赖注入与控制反转


依赖倒置原则(Dependence Inversion Principle)

DIP是面向对象设计原则之一。
传统软件设计中,上层代码依赖于下层代码,当下层出现变动时, 上层代码也要相应变化,维护成本较高。而DIP的核心思想是上层定义接口,下层实现这个接口, 从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。这是一种经实践证明的有效策略。

控制反转(Inversion of Control)

IoC则是DIP的一种具体思路,DIP只是一种理念、思想,而IoC是一种实现DIP的方法。 IoC的核心是将类(上层)所依赖的单元(下层)的实例化过程交由第三方来实现。

当调用者需要被调用者的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例,但在这里,创建被调用者的工作不再由调用者来完成,而是将被调用者的创建移到调用者的外部,从而反转被调用者的创建,消除了调用者对被调用者创建的控制,因此称为控制反转。

依赖注入(Dependence Injection)

DI是IoC的一种设计模式,按照DI的模式,就可以实现IoC。 DI的实质就是把一个类不可能更换的部分和可更换的部分分离开来,通过注入的方式来使用,从而达到解耦的目的。

这里我们举个例子(旅行的接口)说明一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
interface Travel
{
public function travelAlgorithm();
}

/**
*乘坐飞机
*/
class AirPlanelStrategy implements Travel
{
public function travelAlgorithm()
{
echo"travelbyAirPlain\r\n";
}
}

/**
*乘坐火车
*/
class TrainStrategy implements Travel
{
public function travelAlgorithm()
{
echo"travelbyTrain\r\n";
}
}

/**
*
*算法解决类,以提供客户选择使用何种解决方案:
*/
class PersonContext
{
private $strategy = null;

public function __construct(Travel $travel)
{
$this->strategy=$travel;
}

/**
*旅行
*/
public function travel()
{
return$this->strategy->travelAlgorithm();
}

}
// 乘坐火车旅行
$person = new PersonContext(new TrainStrategy());
$person->travel();

// 改乘飞机
$person =PersonContext(new AirPlanelStrategy());
$person->travel();

当我们更换交通工具时,只需要去增加Travel接口的实现,修改下实现的代码接口,无需去改动核心代码。

控制反转容器(IoC Container)

当项目比较大时,依赖关系可能会十分复杂。 而IoC Container提供了动态地创建、注入依赖单元,映射依赖关系等功能,方便开发者使用,并大大缩减了许多代码量。

因为我们的框架比较简单,我们不妨实现下Ioc容器(主要参考了Yii的di容器)。

代码实现用到了PHP的反射Api,有疑问的不妨先看看手册:

还记得PSR吗?PSR11是关于依赖注入容器接口规范:

然后我们利用composer执行

composer require psr/container

我们在library/Components新建Container实现ContainerInterface,在library/Exceptions下新建ContainerException实现Psr\Container\ContainerExceptionInterface,新建ContainerNotFoundException实现Psr\Container\NotFoundExceptionInterface。

我们主要来实现一下Container代码,我们预先定义三个属性,用以保存对象、依赖及依赖的定义信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace Library\Components;

use Library\Exceptions\ContainerException;
use Library\Exceptions\ContainerNotFoundException;
use Psr\Container\ContainerInterface;
use ReflectionClass;

class Container implements ContainerInterface
{
// 用于保存依赖的定义,以对象名称为键
private $definitions = [];

// 用于缓存ReflectionClass对象,以对象名称为键
private $reflections = [];

// 用于缓存依赖信息,以对象名称为键
private $dependencies = [];

public function has($class)
{
return isset($this->definitions[$class]);
}

public function get($class)
{
...
}
}

我们先添加一个set方法,用以定义,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public function set($class, $definition = [])
{
$this->definitions[$class] = $this->normalizeDefinition($class, $definition);
return $this;
}

protected function normalizeDefinition($class, $definition)
{
// $definition 是空的转换成 ['class' => $class] 形式
if (empty($definition)) {
return ['class' => $class];

// $definition 是字符串,转换成 ['class' => $definition] 形式
} elseif (is_string($definition)) {
return ['class' => $definition];

// $definition 是对象,则直接将其作为依赖的定义
} elseif (is_object($definition)) {
return $definition;

// $definition 是数组则确保该数组定义了 class 元素
} elseif (is_array($definition)) {
if (!isset($definition['class'])) {
$definition['class'] = $class;
}
return $definition;
// 这也不是,那也不是,那就抛出异常算了
} else {
throw new ContainerException(
"不支持的类型: \"$class\": " . gettype($definition));
}
}

知识点:

  • gettype — 获取变量的类型

然后我们重点实现get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public function get($class)
{
// 加入未作set操作,我们依旧可以构建
if (!isset($this->definitions[$class])) {
return $this->build($class);
}

$definition = $this->definitions[$class];
if (is_array($definition)) {
$concrete = $definition['class'];
unset($definition['class']);

if ($concrete === $class) {
$object = $this->build($class, $definition);
} else {
$object = $this->get($concrete);
}
} elseif (is_object($definition)) {
return $this->_singletons[$class] = $definition;
} else {
throw new ContainerNotFoundException('不能识别的对象类型: ' . gettype($definition));
}

return $object;

}

build方法如下,主要是构建出对象并实现注入,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function build($class, $params = [])
{
try {
// 通过反射api获取对象
$reflector = $this->getReflectionClass($class);

// 获取依赖关系数组
$dependencies = $this->getDependencies($class, $reflector);

// 创建一个类的新实例,给出的参数将传递到类的构造函数.
$reflector = $reflector->newInstanceArgs($dependencies);

return $reflector;
} catch (\Throwable $t) {
throw new ContainerException('反射出错');
}
}

获取对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function getReflectionClass($class)
{
if (isset($this->reflections[$class])) {
return $this->reflections[$class];
}

$reflector = new ReflectionClass($class);
if (!$reflector->isInstantiable()) {
throw new ContainerException("不能实例化".$class);
}

return $this->reflections[$class] = $reflector;
}

获取依赖关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public function getDependencies($class, $reflector)
{
// 判断是否有缓存依赖关系
if (isset($this->dependencies[$class])) {
return $this->dependencies[$class];
}
$constructor = $reflector->getConstructor();

#如果没有构造函数, 直接实例化并返回
if (is_null($constructor)) {
return $this->dependencies[$class] = [];
}

$parameters = $constructor->getParameters();

$dependencies = [];
foreach ($parameters as $className) {
$dependency = $className->getClass();

if (is_null($dependency)) {
$dependencies[] = $this->resolveNoneClass($className);
} else {
// 先取出容器中绑定的类 否则自动绑定
$dependencies[] = $this->get($dependency->getName());
}
}

$this->dependencies[$class] = $dependencies;

return $dependencies;
}

public function resolveNoneClass($class)
{
// 有默认值则返回默认值
if ($class->isDefaultValueAvailable()) {
return $class->getDefaultValue();
}
throw new ContainerException('不能解析参数');
}

到这里,我们基本就完成了一个完整的IOC Container的代码。

我们来写一个demo:

1
2
3
4
5
6
7
8
<?php

namespace App\Https\Controllers;

class C
{

}
1
2
3
4
5
6
7
8
9
10
11
<?php

namespace App\Https\Controllers;

class B
{
public function __construct(C $c)
{
$this->ccc = $c;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace App\Https\Controllers;

class A
{
public function __construct(B $b, C $c)
{
$this->bbb = $b;
$this->ccc = $c;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

namespace App\Https\Controllers;

use Library\Components\Container;
use Library\Https\Controller;

class IndexController extends Controller
{
public function index()
{
$contain = new Container();
$contain->set('App\\Https\\Controllers\\A');
p($contain->get('App\\Https\\Controllers\\A'));
}
}

我们可以看到的结果:

image

是不是代码简洁很多了,不愿因为需要创建一个A对象,而先去实例化B和C,这些都是由我们完成的IOC Container去实现了。

总结

这一节我们实现了IOC容器,然而你如果仔细去想,我们会发现我们可以让容器更加强大,比如单例对象的实现,比如依赖的扩展(兼容对象参数注入,数组参数注入等)。这些你可以自行实现,我也在源代码做了简单的扩展,大家可以思考试着实现一下,当然也可以看看开源框架Laravel、Yii的服务容器的实现。