0%

设计模式-控制反转及其依赖注入(2)

上节我们介绍了控制反转及依赖注入的实现

最后在调用测试时:

1
2
3
4
5
6
7
8
//用宝剑的英雄
$class = new Hero(new Gun('倚天'));
$class->myWeapon();
//我的倚天打起来唰唰唰~
//用枪的英雄
$class = new Hero(new Sword('沙漠之鹰'));
$class->myWeapon();
//我的沙漠之鹰打起来砰砰砰~

前言


我们看到,注入时需要实例化好所依赖的对象,再传到Hero类中,虽然通过依赖注入解决了解耦问题,但是在实际使用中,比较麻烦,因为每次都需要手动实例化依赖,再传递,这对于复杂大量的依赖关系,手动解决明显力不从心。因此,项目中需要一个自动化的依赖注入管理机制,这就是IoC容器;

IoC容器:一个封装了依赖注入DI的框架,实现了动态创建注入依赖对象,管理依赖关系,管理对象声明周期等功能

核心实现,一般分为绑定(注册)对象生成器创建对象注入依赖这两个核心步骤

IoC容器


绑定(注册)对象生成器

绑定:指的是将类于生成类对象的代码记录,对应,绑定起来。这样,在需要该类对象时,直接执行类对应的生成代码,就可以得到所需的对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 绑定类的生成器
*
* @param $className 类名或者映射名,类的标志
* @param $generator 对应实例化或者可生成此类对象的代码
*
* @throws \Exception
*
* @author mma5694@gmail.com
* @date 2017年5月7日00:42:02
*/
public static function bind($className, $generator)
{
//检测参数是否为合法的可调用结构
if (is_callable($generator)) {
self::$generatorList[$className] = $generator;
} else {
throw new \Exception('对象生成器不是可调用的结构!');
}
}

注意bind方法,就是上面说的绑定(注册)对象生成器的实现,一般bind方法需要两个参数:

  • 第一个就是类的标志,通常就是带有命名空间的类名称,也可以是自定义的类对应标志
  • 第二个参数是一个匿名函数,也就是生成器,执行new的代码,这个参数可以是匿名函数、函数、类方法或者其他可执行的结构都是可以的

这样其实就是要用户提供类和该类对象的生成代码,将其对应,需要该类对象时再执行,但是注册时,并不调用生成类的代码,而仅仅时先存储起来(真精妙呀)。下面的例子就是将生成器代码存储到$generatorList数组中;

容器绑定对象生成器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

use ClassFile\Hero;
use IImplements\Sword;
use IImplements\Gun;

//这里我第一个参数用的不带命名空间的类名
IoContainer::bind('Gun', function ($title = '') {
return new Gun($title);
});
IoContainer::bind('Sword', function ($title = '') {
return new Sword($title);
});
IoContainer::bind('Hero', function ($module, $params = []) {
return new Hero(IoContainer::make($module, $params));
});

调用bind()方法,提供类名和实例化类对象的匿名函数,我们的容器就会将类与生成器记录下来,等着需要时实例化生成所需对象

创建对象注入依赖

方式1

先看看完善后的IoContainer类

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
class IoContainer
{
//定义存放类的容器?
protected static $generatorList = [];


/**
* 绑定类的生成器
*
* @param $className 类名或者映射名,类的标志
* @param $generator 对应实例化或者可生成此类对象的代码
*
* @throws \Exception
*
* @author mma5694@gmail.com
* @date 2017年5月7日00:42:02
*/
public static function bind($className, $generator)
{
//检测参数是否为合法的可调用结构
if (is_callable($generator)) {
self::$generatorList[$className] = $generator;
} else {
throw new \Exception('对象生成器不是可调用的结构!');
}
}

/**
* 生成类的对象
*
* @param $className
* @param array $param
*
* @return mixed
* @throws \Exception
*
* @author mma5694@gmail.com
* @date 2017年5月7日00:46:00
*/
public static function make($className, $param = [])
{
if (!isset(self::$generatorList[$className])) {
throw new \Exception('类还没有绑定注册!');
}

return call_user_func_array(self::$generatorList[$className], $param);
}
}

上面代码中的make方法就是用来生成对象的方法,该方法要获取所需的类,然后调用绑定时的生成器函数,来获取对象

通过make生成类对象:

1
2
3
4
$hero1 = IoContainer::make('Hero',['Sword',['屠龙刀']]);
$hero1->myWeapon(); //我的屠龙刀打起来唰唰唰~
$hero2 = IoContainer::make('Hero',['Gun',['AK-47']]);
$hero2->myWeapon(); //我的AK-47打起来砰砰砰~

方式2

我们将所有的映射一一对应写入一个配置文件中,在容器类的构造方法中,引入配置文件中的所有对应关系,自动完成绑定(注册)对象生成器

1
2
3
4
5
6
7
config.php 

return [
'Sword'=>function($title=''){return new \IImplements\Sword($title);},
'Gun'=>function($title=''){return new \IImplements\Gun($title);},
'Hero'=>function($module,$params = []){return new \ClassFile\Hero(IoContainer\IoContainer::make($module,$params));},
];
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
58
59
60
61
class IoContainer
{
//定义存放类的容器?
protected static $generatorList = [];

//配置文件注册
public function __construct()
{
$config = dirname(dirname(__FILE__)) . "/Config/config.php";
$config = include $config;
foreach ($config as $key => $value) {
if (is_callable($value)) {
self::$generatorList[$key] = $value;
} else {
throw new \Exception('对象生成器不是可调用的结构!');
}
}
}

/**
* 绑定类的生成器
*
* @param $className 类名或者映射名,类的标志
* @param $generator 对应实例化或者可生成此类对象的代码
*
* @throws \Exception
*
* @author mma5694@gmail.com
* @date 2017年5月7日00:42:02
*/
public static function bind($className, $generator)
{
//检测参数是否为合法的可调用结构
if (is_callable($generator)) {
self::$generatorList[$className] = $generator;
} else {
throw new \Exception('对象生成器不是可调用的结构!');
}
}

/**
* 生成类的对象
*
* @param $className
* @param array $param
*
* @return mixed
* @throws \Exception
*
* @author mma5694@gmail.com
* @date 2017年5月7日00:46:00
*/
public static function make($className, $param = [])
{
if (!isset(self::$generatorList[$className])) {
throw new \Exception('类还没有绑定注册!');
}

return call_user_func_array(self::$generatorList[$className], $param);
}
}

这样就当调用容器类时,会自动绑定(注册)生成器

1
2
3
4
5
$container = new IoContainer();
$hero1 = IoContainer::make('Hero',['Sword',['屠龙刀']]);
$hero1->myWeapon(); //我的屠龙刀打起来唰唰唰~
$hero2 = IoContainer::make('Hero',['Gun',['AK-47']]);
$hero2->myWeapon(); //我的AK-47打起来砰砰砰~

这样也是可以的;

上面的例子就完成了IoC容器的两个基本步骤:绑定和创建

结语


理解了什么是IoC容器, 本文的目的就达到了. 实际使用中(例如laravel)IoC容器的方法会有很多, 例如绑定构造器, 绑定对象实例, 绑定单例, 绑定接口实现等. 具体的使用就要到具体的框架或者产品中应用了

本文示例代码在这里,引用参考文章地址在这里