在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式,重点在于介绍这种年轻的设计模式的适用场景及优势。
首先我们来一个实例,上代码
<?php class A { public function test() { echo 'this is A!<br>'; $b = new B(); $b->test(); } } class B { public function test() { echo 'this is B!<br>'; $c = new C(); $c->test(); } } class C { public function test() { echo 'this is C!<br>'; } } $obj = new A(); $obj->test();
结果是:
this is A! this is B! this is C!
从代码分析,A类依赖B类,B类依赖C类。这是我们最原始的实现思路.这种实现思路很明显会有问题
假如我们现在B类修改下,代码如下:
class B { public $name; public function __construct($name) { $this->name = $name; } public function test() { echo 'this is B'.$this->name.'!<br>'; $c = new C(); $c->test(); } }
此时再看我们原来A类test方法直接调用明显会有问题,于是此时我们需要将原来代码:
class A { public function test() { echo 'this is A!<br>'; $b = new B(); $b->test(); } }
修改成:
class A { public function test() { echo 'this is A!<br>'; $b = new B('(class B)');//构造的时候多加了一个参数 $b->test(); } }
如果此时C类构造方法也变动了呢,B的方法里面也同样受影响,很明显可用性非常低
为了解耦,此时我们用到第二种思路:构造器注入,直接上代码:
<?php class A { public $obj; public function __construct(B $b) { $this->obj = $b; } public function test() { echo 'this is A!<br>'; $this->obj->test(); } } class B { public $obj; public function __construct(C $c) { $this->obj = $c; } public function test() { echo 'this is B!<br>'; $this->obj->test(); } } class C { public function test() { echo 'this is C!<br>'; } } $c = new C(); $b = new B($c); $obj = new A($b); $obj->test();
这种方法可以解决第一种方法,如果依赖的类构造方法(比如B类)有所改动,A类不需要改动任何代码。但是久而久之,这种方法毛病也出来了,我们首先必须要先实例化C类,再实例化B类,最后再实例化A类。
了解到第二种方案需要优化,于是出现了基本成型的第三种方案,此时我们用到了container(容器)和instance(实例),直接上代码:
class Container { public static $_definations = []; public function get($class) { //当前类所依赖的类 $depends = []; $tc = new ReflectionClass($class); //得到构造方法 $constructor = $tc->getConstructor(); //得到构造方法的参数 if($constructor !== NULL) { foreach($constructor->getParameters() as $parameter) { if($parameter->isDefaultValueAvailable()) { $depends[] = $parameter->getDefaultValue(); } else { $pc = $parameter->getClass(); $instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName()); $depends[] = $instance; } } } foreach($depends as $k => $v) { if($v instanceof Instance) { if($v->id !== NULL) { $depends[$k] = $this->get($v->id); } } } $tm_instance = $tc->newInstanceArgs($depends); return $tm_instance; } } class Instance{ /** * @var 类唯一标示 */ public $id; /** * 构造函数 * @param string $id 类唯一ID * @return void */ public function __construct($id) { $this->id = $id; } /** * 获取类的实例 * @param string $id 类唯一ID * @return Object Instance */ public static function getInstance($id) { return new self($id); } } class Base{ } class A extends Base{ private $instanceB; public function __construct(B $instanceB) { $this->instanceB = $instanceB; } public function test() { echo 'this is A!<br/>'; $this->instanceB->test(); } } class B extends Base{ private $instanceC; public function __construct(C $instanceC) { $this->instanceC = $instanceC; } public function test() { echo 'this is B!<br/>'; return $this->instanceC->test(); } } class C extends Base{ public function test() { echo 'this is C!'; } } $container = new Container(); $obj_a = $container->get('A'); $obj_a->test();
此方法有参考yii2中yii2\di\container实现,只是将它简化了,更容易看懂。重点看看container的get方法
现在我们增加set方法,自定义函数,直接上代码精简版
class Container { /** *@var array 存储各个类的定义 以类的名称为键 */ public static $_definations = []; public function get($class, $params = [], $props = []) { if(!isset(self::$_definations[$class])) return $this->build($class, $params, $props); //如果已经被定义过的 $_defination = self::$_definations[$class]; if(is_callable($_defination, true)) { return call_user_func($_defination, $this, $params, $props); } else if(is_object($_defination)) { return $_defination; } else { throw new Exception($class . '声明错误'); } } public function set($class, $_defination) { self::$_definations[$class] = $_defination; } public function build($class, $params, $props) { //当前类所依赖的类 $depends = []; $tc = new ReflectionClass($class); //得到构造方法 $constructor = $tc->getConstructor(); //得到构造方法的参数 if($constructor !== NULL) { foreach($constructor->getParameters() as $parameter) { if($parameter->isDefaultValueAvailable()) { $depends[] = $parameter->getDefaultValue(); } else { $pc = $parameter->getClass(); $instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName()); $depends[] = $instance; } } } foreach($depends as $k => $v) { if($v instanceof Instance) { if($v->id !== NULL) { $depends[$k] = $this->get($v->id); } } } $tm_instance = $tc->newInstanceArgs($depends); return $tm_instance; } }
增加了一个set方法,并将get方法稍微改版了一下,现在我们看看怎么调用set方法:
$container = new Container(); $container->set('foo', function($container, $params, $config){ print_r($params); print_r($config); }); $container->get('foo', ['p1' => 'pv1'], ['c1' => 'cv1']);
输出结果为:
Array ( [p1] => pv1 ) Array ( [c1] => cv1 )
再看看另外一种情况调用:
class Test { public function mytest() { echo 'this is a test'; } } $container->set('testObj', new Test()); $test = $container->get('testObj'); $test->mytest();
输出结果为:
this is a test
上面的代码只是作者粗略的写了下简单版本,很多地方不是太完善,这版本是为了理解yii2的container打下基础,稍后会出yii2的依赖注入源码分析
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。