在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式,重点在于介绍这种年轻的设计模式的适用场景及优势。


首先我们来一个实例,上代码

<?phpclassA{publicfunctiontest(){echo'thisisA!<br>';$b=newB();$b->test();}}classB{publicfunctiontest(){echo'thisisB!<br>';$c=newC();$c->test();}}classC{publicfunctiontest(){echo'thisisC!<br>';}}$obj=newA();$obj->test();


结果是:

thisisA!thisisB!thisisC!


从代码分析,A类依赖B类,B类依赖C类。这是我们最原始的实现思路.这种实现思路很明显会有问题

假如我们现在B类修改下,代码如下:

classB{public$name;publicfunction__construct($name){$this->name=$name;}publicfunctiontest(){echo'thisisB'.$this->name.'!<br>';$c=newC();$c->test();}}


此时再看我们原来A类test方法直接调用明显会有问题,于是此时我们需要将原来代码:

classA{publicfunctiontest(){echo'thisisA!<br>';$b=newB();$b->test();}}

修改成:

classA{publicfunctiontest(){echo'thisisA!<br>';$b=newB('(classB)');//构造的时候多加了一个参数$b->test();}}

如果此时C类构造方法也变动了呢,B的方法里面也同样受影响,很明显可用性非常低


为了解耦,此时我们用到第二种思路:构造器注入,直接上代码:

<?phpclassA{public$obj;publicfunction__construct(B$b){$this->obj=$b;}publicfunctiontest(){echo'thisisA!<br>';$this->obj->test();}}classB{public$obj;publicfunction__construct(C$c){$this->obj=$c;}publicfunctiontest(){echo'thisisB!<br>';$this->obj->test();}}classC{publicfunctiontest(){echo'thisisC!<br>';}}$c=newC();$b=newB($c);$obj=newA($b);$obj->test();

这种方法可以解决第一种方法,如果依赖的类构造方法(比如B类)有所改动,A类不需要改动任何代码。但是久而久之,这种方法毛病也出来了,我们首先必须要先实例化C类,再实例化B类,最后再实例化A类。


了解到第二种方案需要优化,于是出现了基本成型的第三种方案,此时我们用到了container(容器)和instance(实例),直接上代码:

classContainer{publicstatic$_definations=[];publicfunctionget($class){//当前类所依赖的类$depends=[];$tc=newReflectionClass($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($dependsas$k=>$v){if($vinstanceofInstance){if($v->id!==NULL){$depends[$k]=$this->get($v->id);}}}$tm_instance=$tc->newInstanceArgs($depends);return$tm_instance;}}classInstance{/***@var类唯一标示*/public$id;/***构造函数*@paramstring$id类唯一ID*@returnvoid*/publicfunction__construct($id){$this->id=$id;}/***获取类的实例*@paramstring$id类唯一ID*@returnObjectInstance*/publicstaticfunctiongetInstance($id){returnnewself($id);}}classBase{}classAextendsBase{private$instanceB;publicfunction__construct(B$instanceB){$this->instanceB=$instanceB;}publicfunctiontest(){echo'thisisA!<br/>';$this->instanceB->test();}}classBextendsBase{private$instanceC;publicfunction__construct(C$instanceC){$this->instanceC=$instanceC;}publicfunctiontest(){echo'thisisB!<br/>';return$this->instanceC->test();}}classCextendsBase{publicfunctiontest(){echo'thisisC!';}}$container=newContainer();$obj_a=$container->get('A');$obj_a->test();

此方法有参考yii2中yii2\di\container实现,只是将它简化了,更容易看懂。重点看看container的get方法


现在我们增加set方法,自定义函数,直接上代码精简版

classContainer{/***@vararray存储各个类的定义以类的名称为键*/publicstatic$_definations=[];publicfunctionget($class,$params=[],$props=[]){if(!isset(self::$_definations[$class]))return$this->build($class,$params,$props);//如果已经被定义过的$_defination=self::$_definations[$class];if(is_callable($_defination,true)){returncall_user_func($_defination,$this,$params,$props);}elseif(is_object($_defination)){return$_defination;}else{thrownewException($class.'声明错误');}}publicfunctionset($class,$_defination){self::$_definations[$class]=$_defination;}publicfunctionbuild($class,$params,$props){//当前类所依赖的类$depends=[];$tc=newReflectionClass($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($dependsas$k=>$v){if($vinstanceofInstance){if($v->id!==NULL){$depends[$k]=$this->get($v->id);}}}$tm_instance=$tc->newInstanceArgs($depends);return$tm_instance;}}


增加了一个set方法,并将get方法稍微改版了一下,现在我们看看怎么调用set方法:

$container=newContainer();$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)


再看看另外一种情况调用:

classTest{publicfunctionmytest(){echo'thisisatest';}}$container->set('testObj',newTest());$test=$container->get('testObj');$test->mytest();


输出结果为:

thisisatest


上面的代码只是作者粗略的写了下简单版本,很多地方不是太完善,这版本是为了理解yii2的container打下基础,稍后会出yii2的依赖注入源码分析