当前位置: 首页 > 后端技术 > PHP

浅谈依赖注入与控制反转

时间:2023-03-30 05:10:59 PHP

前言:设计模式其实是一个很空洞的东西。有几十种设计模式。有人认为工厂模式和单例模式足以解决大部分问题。有人认为任何设计模式都会让开发变得更加“复杂”和“低效”。所以不要过分追求它的实际意义和作用,否则会坠入云端。但无论如何,你还是需要在实际工作中去了解它们。下面我们从PHP的角度来谈谈依赖注入、控制反转、反射等概念。如有错误,还望路过的大神多多指点。首先,设置场景。如果一个类需要数据库连接,最简单的方法可能是:classexample{private$_db;函数__construct(){包括“./Lib/Db.php”;$this->_db=newDb("localhost","root","123456","test");}函数getList(){$this->_db->query("....");但其实稍有经验的同学是不会这么写的,因为一旦用db的类越来越多,一旦db发生变化,那岂不是每个文件都要修改?这就是编程中的耦合问题。所有类都严重依赖于“./Lib/Db.php”文件。OK,为了解决这个问题,工厂模式出现了,我们新建一个Factory工厂类:classFactory{publicstaticfunctiongetDb(){include"./Lib/Db.php";returnnewDb("localhost","root","123456","test");}}类示例{private$_db;函数__construct(){$this->_db=Factory::getDb();}functiongetList(){$this->_db->query("...");如果我们使用db模块,那么直接从工厂中取出Factory::getDb(),这样好像就解决了问题。但真的是这样吗?不是,这只是把程序和db模块的耦合转移给了Factory。后面业务一变,Factory变了,每个文件还是要改的。如何解决?我们没有从示例类中获取db组件,而是将db组件注入到示例类中classexample{private$_db;functiongetList(){$this->_db->query("...");//执行查询}//从外部注入数据库连接functionsetDb($connection){$this->_db=$connection;}}$示例=新示例();$example->setDb(Factory::getDb());//注入数据库连接$example->getList();这样例子就不需要关心db组件是怎么来的了,只需要暴露一个注入方法即可。这就是DI/依赖注入(DependencyInjection),其内部不处理依赖,而是将依赖作为参数传入,以降低程序耦合。然后我们的项目继续,使用文件处理类,图像处理类,我们可能会写$example->setDb(Factory::getDb());//注入数据库连接$example->setFile(Factory::getFile());//注入文件处理类$example->setImage(Factory::getImage());//注入图像处理类但是好像不行,每次都要写那么多代码,所以我们得写一个工厂方法类Factory{publicstaticfunctiongetExample(){$example=newexample();$example->setDb(Factory::getDb());//注入数据库连接$example->setFile(Factory::getFile());//注入文件处理类$example->setImage(Factory::getImage());//注入图像处理类return$example;}}示例也不是直接新的。我们通过Factory::getExample()获取它。不过,这又是不是有点似曾相识的感觉呢?就像上面第一次使用工厂类时一样,依赖于工厂。于是又有了容器的概念。类示例{private$_di;函数__construct(Di&$di){$this->_di=$di;}//通过di容器函数getList()获取db实例{$this->_di->get('db')->query("...");}}$di=newDi();$di->set("db",function(){returnnewDb("localhost","root","root","test");});$example=newexample($di);$example->getList();Di是一个容器,用来存放各种可扩展的组件,需要注入的时候调用$di->set()方法可以将组件注入。组件可以通过程序中的$di->get()获取。这样,被调用的组件(db)就不是调用者(example)创建的了,而是由Di容器创建的。调用者失去控制权,而容器获得控制权,发生控制权转移,因此称为控制反转(Inversion)。ofControl)但是这样一来,一些比较强迫症的同学发现,每个类都注入到容器中有点麻烦。没错,其实注入容器这个动作是可以交给另外一个程序来处理的,那就是反射。_di->get('db')->query("...");}}//di容器类Di{public$_container;公共函数get($cls){返回$this->_container[$cls];}publicfunctionset($cls,$_instance){$this->_container[$cls]=$_instance;}}//db组件类db{privatestatic$_instance;//保存单例//单例方法publicstaticfunctiongetInstance(){if(!(self::$_instanceinstanceofself)){self::$_instance=new自己;}返回自我::$_实例;}//查询方法publicfunctionquery($sql){echo$sql;}}$di=newDi();//实例化容器$di->set('db',db::getInstance());//注入数据库实例$reflector=newReflectionClass('example');//反射实例,可以通过反射获取类的所有信息$reflector->getDefaultProperties();//示例属性$reflector->getDocComment();//评论$instance=$reflector->newInstanceArgs();//相当于实例化反射的示例类$instance->_di=$di;//注入di容器$reflector->getmethod('getList')->invoke($instance);//通过反射调用示例类的方法我们可以得到类的所有信息,包括方法,方法名,属性,甚至注释等。通过反射,我们可以方便地控制程序中使用的类,对其进行扩展、修改和监控。通常反射广泛应用于插件开发和框架开发。在框架开发中,反射、依赖注入、控制反转也会一起使用,使程序具有很强的可控性和可扩展性。博客链接:浅谈依赖注入与控制反转