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

软件工程入门-轻松理解依赖注入(DI)和IoC容器

时间:2023-03-29 14:48:43 PHP

为了更好的理解依赖注入(DI)和IoC容器的概念,我们先设计一个场景。现在你饿了准备吃晚饭,那么你可能要做的事情就是买食材、做食材、享受美食。晚餐类的设计应该是这样的:,您可能还需要一种能源来加热食物,例如,如果您选择燃气。那么gas的类设计应该是这样的:fire();}...}为了节省篇幅,上面的代码使用了'...'来隐藏部分代码,后面的文章类似。那么调用过程是这样的:$dinner=new\Ioc\Dinner();$dinner->cookFood();上面的设计创建了一个依赖,Dinner依赖Gas,这个依赖使得两个类耦合在一起,这种设计的缺陷是显而易见的。煤气用完了怎么办,天然气改成煤气怎么办,那晚饭就毁了。从代码上看,一旦Gas类在某些环境下不能再运行,一旦需要更改Gas类名,Dinner就会很被动,每次调用都需要用new实例化一次Gas,这是一个浪费系统资源。IOC的全称是InversionofControl,译为控制反转。像上面的设计,Dinner被称为主类,Gas被称为子类。子类的实例化由主类控制。本方法为阳性对照。如果子类的实例化不受主类控制,大概就是控制反转。如何解决这种强耦合关系呢?一种解决方案是使用工厂模式。工厂模式工厂模式非常简单。它使用代理类来帮助您批量实例化“子类”。Agent类如下:energy=Agent::useEnergy();$this->energy->fire();}...}这样,Dinner就不再直接依赖Gas,而是通过一个Agent代理来控制创作的能量。但是去掉了对Gas的依赖,带来了对Agent的依赖。虽然更换Agent的可能性不大,但谁能保证。依赖注入(DI)就是彻底去除依赖,子类的调用代码必须从主类中去除。否则,改变子类的类名等变化会影响所有依赖它的主类的代码,所有依赖它的主要类都得相应改代码,可以说是牵一发而动全身。依赖注入的一种方式是将依赖对象从外部通过参数注入到类中。如下更改Dinner类:...publicfunctionsetEnergy($energy){$this->energy=$energy;}publicfunctioncookFood(){$this->energy->fire();}...添加一个setEnergy方法来注入依赖对象。那么调用过程就会变成:$dinner=new\Ioc\Dinner();$dinner->setEnergy(\Ioc\Agent::useEnergy());$dinner->cookFood();以上是一种依赖注入的例子。晚餐完全摆脱了对能量类的依赖。但是会出现新的问题。cookFood不仅仅依赖能量,还可能依赖厨具、调料等,那么调用过程会是这样:$dinner->setEnergy(...);$dinner->setKitchen(...);$dinner->setSauce(...);$dinner->cookFood();每次调用很多set方法就更不科学了。取而代之的是,只需将所有设置的方法交给一个TopAgent。TopAgent类如下:setEnergy(Agent::useEnergy());$dinner->setKitchen(代理::useKitchen());$dinner->setSauce(Agent::useSauce());返回$晚餐;}}这样,调用过程就变得简单了。至此,基本实现了Dinner的依赖注入。但是仔细一看,一瞬间,好像又回到了原来的问题,不,好像不是,就是那样!Dinner类解除了外部类的依赖,但是却变成了TopAgent的依赖类,TopAgent不就是原来的Dinner吗?绕了一大圈,还是原点,一次又一次,回到不切实际的例子。一个实用而优雅的解决方案是为依赖实例提供一个容器。那就是IOC容器。IOC容器IOC容器首先是一个类注册表,其次它是一种更高级的依赖注入形式。它其实和工厂Factory一样,都是一个代理类,只是实现机制不同而已。IOC容器的设计模式称为注册器模式。Container类如下:container=$container;}publicfunctionbuyFood(){//}publicfunctioncookFood(){$this->container->get('energy')->fire();}publicfunctioneatFood(){//}}因此,调用过程可以漂亮地写成:\Ioc\Container::set('energy',function(){return\Ioc\Agent::use能量();});$晚餐=\Ioc\Agent::bindDinner(\Ioc\Agent::bindContainer());$晚餐->cookFood();将容器Container注入到Dinner中,实现所有类的全面解耦。