php依赖注入
在软件工程领域,依赖注入(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的依赖注入源码分析
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。