前言好的设计会提高程序的复用性和可维护性,间接提高开发人员的工作效率。今天来说说依赖注入,很多框架都会用到。一些概念要搞清楚什么是依赖注入以及如何依赖注入,首先我们需要弄清楚一些概念。DIP(DependenceInversionPrinciple)依赖倒置原则:程序应该依赖于抽象接口,而不是具体实现。IOC(InversionofControl)控制反转:一种遵循依赖倒置原则的代码设计方案,依赖的创建(控制)由主动变为被动(倒置)。DI(DependencyInjection)依赖注入:一种控制反转的具体实现方式。通过参数的方式从外部传入依赖,将依赖的创建由主动变为被动(实现控制反转)。光说理论有点难以理解,还是用代码来举例吧。首先我们看一段不依赖倒置时的代码:classController{protected$service;publicfunction__construct(){//主动创建一个依赖$this->service=newService(12,13);}}类服务{protected$model;受保护的$计数;公共函数__construct($param1,$param2){$this->count=$param1+$param2;//主动创建依赖$this->model=newModel('test_table');}}类模型{受保护的$表;公共函数__construct($table){$this->table=$table;}}$controller=新控制器;上面代码的依赖是Controller依赖Service,Service依赖Model。从控制的角度来说,Controller主动创建依赖Service,Service主动创建依赖Model。依赖是需求方内部产生的,需求方需要关心依赖的具体实现。这样的设计使得代码耦合度很高,每次底层发生变化(比如参数变化),顶层都必须修改代码。接下来,我们使用依赖注入来实现控制反转,反转依赖:classController{protected$service;//依赖是被动传入的。声明您需要服务类的实例(抽象接口)publicfunction__construct(Service$service){$this->service=$service;}}类服务{protected$model;受保护的$计数;//依赖被动输入publicfunction__construct(Model$model,$param1,$param2){$this->count=$param1+$param2;$this->model=$model;}}类模型{受保护的$table;公共函数__construct($table){$this->table=$table;}}$model=newModel('test_table');$service=新服务($model,12,13);$controller=newController($service);将依赖传递参数的方法从外部传入(即依赖注入),从控制的角度产生依赖从主动创建变为被动注入,依赖关系变得依赖于抽象接口而不是而不是在具体的实现上。此时的代码是解耦的,提高了可维护性。从单元测试的角度来看,依赖注入更方便stub和mock操作,方便测试人员编写更高质量的测试代码。如何依赖注入和自动注入依赖有了上面的一些理论基础,我们对什么是依赖注入以及它能做什么有了一个大致的了解。不过,虽然上面的代码可以用于依赖注入,但是仍然需要手动创建依赖。我们可以创建一个工厂类来帮助我们进行自动依赖注入吗?好的,我们需要一个IOC容器。实现一个简单的IOC容器依赖注入作为构造函数参数传入。自动注入:需要知道需求方需要哪些依赖,通过反射获取只注入该类的实例,其他参数不受影响如何实现自动注入?当然是PHP自带的反射函数啦!注:关于反射是否影响性能,答案是肯定的。但是,相对于数据库连接和网络请求的延迟,反射带来的性能问题在大多数情况下不会成为应用程序的性能瓶颈。1.原型首先创建一个Container类,getInstance方法://获取一个反射实例$constructor=$reflector->getConstructor()的构造方法;//获取反射实例构造方法的参数$di_params=[];if($constructor){foreach($constructor->getParameters()as$param){$class=$param->getClass();}if($class){//如果参数是一个类,创建一个实例$di_params[]=new$class->name;}}}$di_params=array_merge($di_params,$params);//创建实例return$reflector->newInstanceArgs($di_params);}}这里我们在获取构造方法的参数时使用了ReflectionClass类。这个类包含的方法和用法大家可以去官方文档了解一下,这里不再赘述。好的,通过getInstance方法,我们可以尝试自动依赖注入:classA{public$count=100;}classB{protected$count=1;公共函数__construct(A$a,$count){$this->count=$a->count+$count;}publicfunctiongetCount(){return$this->count;}}$b=Container::getInstance(B::class,[10]);var_dump($b->getCount());//resultis1102.Advanced上面的代码虽然可以进行自动依赖注入,但是问题是只能注入一层。如果A类也有依赖怎么办?ok,我们需要修改代码:classContainer{publicstaticfunctiongetInstance($class_name,$params=[]){//获取反射实例$reflector=newReflectionClass($class_name);//获取反射实例的构造方法$constructor=$reflector->getConstructor();//获取反射实例构造函数的参数$di_params=[];if($constructor){foreach($constructor->getParameters()as$param){$class=$param->getClass();}if($class){//如果参数是类,则创建一个实例,并对该实例进行依赖注入$di_params[]=self::getInstance($class->name);}}}$di_params=array_merge($di_params,$params);//创建实例return$reflector->newInstanceArgs($di_params);}}测试一下:C类{public$count=20;}A类{public$count=100;公共函数__construct(C$c){$this->count+=$c->count;}}B类{受保护的$count=1;民众函数__construct(A$a,$count){$this->count=$a->count+$count;}publicfunctiongetCount(){return$this->count;}}$b=Container::getInstance(B::class,[10]);var_dump($b->getCount());//resultis130上面的代码使用递归完成了多层依赖的注入关系,程序中的依赖层级一般都不是特别深。递归不会造成内存遗漏。3.单例有些类在整个程序生命周期中都会被频繁使用。为了避免依赖注入不断产生新的实例,我们需要IOC容器支持单例模式,已经是单例的实例依赖可以直接获取,节省资源。为Container添加单例相关的方法:classContainer{protectedstatic$_singleton=[];//给单例添加一个实例publicstaticfunctionsingleton($instance){if(!is_object($instance)){thrownewInvalidArgumentException("Objectneed!");}$class_name=get_class($instance);//单例不存在,创建if(!array_key_exists($class_name,self::$_singleton)){self::$_singleton[$class_name]=$instance;}}//获取单例实例publicstaticfunctiongetSingleton($class_name){returnarray_key_exists($class_name,self::$_singleton)?self::$_singleton[$class_name]:NULL;}//销毁一个单例实例publicstaticfunctionunsetSingleton($class_name){self::$_singleton[$class_name]=NULL;}}transformgetInstancemethod:publicstaticfunctiongetInstance($class_name,$params=[]){//获取反射实例$reflector=newReflectionClass($class_name);//获取反射实例$co的构造方法nstructor=$reflector->getConstructor();//获取反射实例构造函数的参数$di_params=[];if($constructor){foreach($constructor->getParameters()as$param){$class=$param->getClass();}if($class){//如果依赖是单例,直接获取$singleton=self::getSingleton($class->name);$di_params[]=$单例?$单例:self::getInstance($class->name);}}}$di_params=array_merge($di_params,$params);//创建实例return$reflector->newInstanceArgs($di_params);}4.通过依赖注入解决运行方法类之间的依赖注入。我们还需要一个以依赖注入方式运行方法的功能,它可以注入任何方法的依赖。这个函数在实现路由分发到控制器方法时非常有用。添加运行方法publicstaticfunctionrun($class_name,$method,$params=[],$construct_params=[]){if(!class_exists($class_name)){thrownewBadMethodCallException("Class$class_nameisnotfound!");}if(!method_exists($class_name,$method)){thrownewBadMethodCallException("undefinedmethod$methodin$class_name!");}//获取实例$instance=self::getInstance($class_name,$construct_params);//获取反射实例$reflector=newReflectionClass($class_name);//获取方法$reflectorMethod=$reflector->getMethod($method);//查询方法的参数$di_params=[];foreach($reflectorMethod->getParameters()as$param){$class=$param->getClass();}如果($class){$singleton=self::getSingleton($class->name);$di_params[]=$单例?$单例:self::getInstance($class->name);}}//运行方法returncall_user_func_array([$instance,$method],array_merge($di_params,$params));}测试:A类{public$count=10;}B类{publicfunctiongetCount(A$a,$count){return$a->count+$count;}}$result=Container::run(B::class,'getCount',[10]);var_dump($result);//结果为20ok,一个简单易用的IOC容器就完成了,自己动手试试吧!完整代码IOCContainer的完整代码可以参考wazsmwazsm/IOCContainer,最初是在我的框架wazsmwazsm/WorkerA中使用的,现在已经作为一个单独的项目使用,单元测试完整,可以用于生产环境
