资讯专栏INFORMATION COLUMN

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

yacheng / 617人阅读

摘要:为了更好的理解依赖注入和容器的概念,我们先设计一个场景。那么调用过程将变成以上就是一种依赖注入的示例。彻底解除了对能源类的依赖。到目前为止,基本上已实现了的依赖注入了。一个实用和优雅的解决方法,是为依赖实例提供一个容器。

为了更好的理解依赖注入 (DI) 和 IOC 容器的概念,我们先设计一个场景。现在你饿了,准备要享用一个晚餐,那么你可能要做的事情有购买食材,烹饪食材,享用食物。

晚餐的类设计看起来应该像是这样的:

</>复制代码

  1. 单拿 cookFood 这步来说,你可能还需要一种能源资源,以便将食材加热,比方说,你选择了燃气。那么燃气的类设计看起来应该像是这样的:

  2. </>复制代码

    1. 好了,现在可以用燃气来加热了。

    2. </>复制代码

      1. ...
      2. class Dinner
      3. {
      4. ...
      5. public function cookFood()
      6. {
      7. $gas = new Gas();
      8. $gas->fire();
      9. }
      10. ...
      11. }
    3. 为节省篇幅,以上代码使用了 ‘…’ 来隐藏了部分代码,以下文章情况类似。那么调用过程是这样的:

    4. </>复制代码

      1. $dinner = new IocDinner();
      2. $dinner->cookFood();
    5. 以上的设计就产生了依赖了,Dinner 依赖了 Gas ,这种依赖让两个类耦合在一起,这种设计的缺陷是明显的。万一燃气用光了呢,万一由天燃气改成煤气了呢,那样子晚餐就泡汤了。在代码看来就是,一旦 Gas 类在某些环境下不能运作了,一旦 Gas 要更改类名了,那么 Dinner 会很被动,况且每一次调用都要 new 实例化一次 Gas ,这很浪费系统资源。

    6. IOC 全称是 Inversion of Control,译作控制反转。像以上设计,Dinner 称作主类, Gas 称作次类, 次类的实例化由主类来控制,这种方式就是正向的控制,如果次类的实例化并不由主类来控制的话,大概就是控制反转的意思了。

    7. 怎么解决这种强耦合关系?一种解决方式是使用工厂模式。

    8. 工厂模式

    9. 工厂模式很简单,就是使用一个代理类来帮助你批量实例化“次类”。
      Agent 类如下:

    10. </>复制代码

      1. Dinner 类如下:

      2. </>复制代码

        1. ...
        2. class Dinner
        3. {
        4. protected $energy;
        5. ...
        6. public function cookFood()
        7. {
        8. $this->energy = Agent::useEnergy();
        9. $this->energy->fire();
        10. }
        11. ...
        12. }
      3. 如此,即可使 Dinner 不再直接依赖 Gas,而交由一个代理 Agent 来控制 energy 的创建。然而,Gas 依赖解除了,又带来了 Agent 的依赖,虽然 Agent 的更改可能性不太,但谁能保证呢。

      4. 依赖注入 (DI)

      5. 在彻底解除依赖,必须要将次类的调用代码从主类中移除才行,否则次类像更改类名这样的改动都将牵动着所在所有依赖它的主类的代码,所有依赖它的主类都要跟着改代码,可谓牵一发而动全身。

      6. 一种依赖注入的方式就是,被依赖的对象通过参数从外部注入到类内部。更改 Dinner 类如下:

      7. </>复制代码

        1. ...
        2. public function setEnergy($energy)
        3. {
        4. $this->energy = $energy;
        5. }
        6. public function cookFood()
        7. {
        8. $this->energy->fire();
        9. }
        10. ...
      8. 添加一个 setEnergy 方法来注入依赖的对象。那么调用过程将变成:

      9. </>复制代码

        1. $dinner = new IocDinner();
        2. $dinner->setEnergy(IocAgent::useEnergy());
        3. $dinner->cookFood();
      10. 以上就是一种依赖注入的示例。Dinner 彻底解除了对能源类的依赖。

      11. 但是新问题还会产生,cookFood 并不只依赖能源,可能还依赖厨具,调味料等。那么调用过程将会是这样的:

      12. </>复制代码

        1. $dinner->setEnergy(...);
        2. $dinner->setKitchen(...);
        3. $dinner->setSauce(...);
        4. $dinner->cookFood();
      13. 每次都要调用很多 set 方法,这样就更不科学了。与其这样,干脆所有 set 方法都交给一个 TopAgent 做好了。
        TopAgent 类如下:

      14. </>复制代码

        1. setEnergy(Agent::useEnergy());
        2. $dinner->setKitchen(Agent::useKitchen());
        3. $dinner->setSauce(Agent::useSauce());
        4. return $dinner;
        5. }
        6. }
      15. 这样,调用过程就变得简单了。

      16. 到目前为止,基本上已实现了 Dinner 的依赖注入了。可认真一看,瞬间,似乎又回到了最初的问题了,不,不是似乎,简直就是了! Dinner 类是解除了外部类的依赖了,但它自己却成了 TopAgent 的依赖类了,而 TopAgent 不正是最初的 Dinner 了吗!绕了一大圈,原来还在原点,一次又一次,我们又回到了不实用的例子中来了。

      17. 一个实用和优雅的解决方法,是为依赖实例提供一个容器。即是 IOC 容器。

      18. IOC 容器

      19. IOC 容器首先是一种类注册器,其次它是一种更高级的依赖注入方式。它和工厂 Factory 其实性质一样,代理类,但实现机制不一样。
        IOC 容器的设计模式叫做注册器模式。
        Container 类如下:

      20. </>复制代码

        1. Agent 类再添加两个方法:

        2. </>复制代码

          1. ...
          2. public static function bindContainer()
          3. {
          4. return new Container();
          5. }
          6. public static function bindDinner(Container $container)
          7. {
          8. return new Dinner($container);
          9. }
          10. ...
        3. Dinner 类接受一个 Container 注入:

        4. </>复制代码

          1. container = $container;
          2. }
          3. public function buyFood()
          4. {
          5. //
          6. }
          7. public function cookFood()
          8. {
          9. $this->container->get("energy")->fire();
          10. }
          11. public function eatFood()
          12. {
          13. //
          14. }
          15. }
        5. 于是,调用过程便可漂亮的写成:

        6. </>复制代码

          1. IocContainer::set("energy", function () {
          2. return IocAgent::useEnergy();
          3. });
          4. $dinner = IocAgent::bindDinner(IocAgent::bindContainer());
          5. $dinner->cookFood();
        7. 将容器 Container 注入到 Dinner 。并实现了所有类的完全解耦。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31816.html

相关文章

  • Spring还可以这么学--IoC(控制反转) / DI(依赖注入)理解

    摘要:对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。控制被反转之后,获得依赖对象的过程由自身管理变为了由容器主动注入。于是,他给控制反转取了一个更合适的名字叫做依赖注入。 Spring还可以这么学--IoC(控制反转) / DI(依赖注入)理解 声明:文章的前三部分参考博文:https://www.cnblogs.com/Nouno...这篇文章首发是在我的个人微信订阅号每天学编...

    atinosun 评论0 收藏0
  • Spring框架学习笔记(一):官方文档介绍,IoC与AOP概念学习

    摘要:构造函数注入通过调用类的构造函数,将接口实现类通过构造函数变量传入。而在中,其使用横切技术,将这类代码从原属的封装对象中提取出来,封装到一个可重用模块中,称为。 最近实习用到Spring的开发框架,但是之前没有接触过,因此希望利用网上的资源来学习以下。 Spring官方给出了非常全面的介绍,非常适合我这种完全的小白……在这一系列学习中,我阅读的主要资源是5.1.2 Reference ...

    mindwind 评论0 收藏0
  • PHP程序员如何理解IoC/DI

    摘要:依赖注入容器管理应用程序中的全局对象包括实例化处理依赖关系。为了解决这样的问题,我们再次回到全局注册表创建组件。参考文章程序员如何理解依赖注入容器补充很多代码背后,都是某种哲学思想的体现。 思想 思想是解决问题的根本思想必须转换成习惯构建一套完整的思想体系是开发能力成熟的标志——《简单之美》(前言) . 成功的软件项目就是那些提交产物达到或超出客户的预期的项目,而且开发过程符合时间和费...

    DataPipeline 评论0 收藏0
  • 深入理解IoC(控制反转)、DI依赖注入

    摘要:引述最近看设计模式以及代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。其中最常见的方式叫做依赖注入,简称,还有一种方式叫依赖查找。在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。 引述 最近看设计模式以及laravel代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。 概念 Io...

    xcc3641 评论0 收藏0
  • Spring笔记01_下载_概述_监听器

    摘要:简单来说,是一个轻量级的控制反转和面向切面的容器框架。变成的支持提供面向切面编程,可以方便的实现对程序进行权限拦截,运行监控等功能。用于反射创建对象,默认情况下调用无参构造函数。指定对象的作用范围。 1.Spring介绍 1.1 Spring概述 Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert...

    reclay 评论0 收藏0

发表评论

0条评论

yacheng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<