上节我们介绍了控制反转及依赖注入的实现
最后在调用测试时:
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容器的方法会有很多, 例如绑定构造器, 绑定对象实例, 绑定单例, 绑定接口实现等. 具体的使用就要到具体的框架或者产品中应用了
本文示例代码在这里 ,引用参考文章地址在这里