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

栏目: 后端 · 发布时间: 5年前

内容简介:为了更好的理解依赖注入 (DI) 和 IOC 容器的概念,我们先设计一个场景。现在你饿了,准备要享用一个晚餐,那么你可能要做的事情有购买食材,烹饪食材,享用食物。晚餐的类设计看起来应该像是这样的:单拿 cookFood 这步来说,你可能还需要一种能源资源,以便将食材加热,比方说,你选择了燃气。那么燃气的类设计看起来应该像是这样的:

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

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

<?php
namespace Ioc;

class Dinner
{
    public function buyFood()
    {
      //
    }

    public function cookFood()
    {
      //
    }

    public function eatFood()
    {
      //
    }
}

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

<?php
namespace Ioc;

class Gas
{
    public function fire()
    {
        echo __CLASS__."::".__FUNCTION__.PHP_EOL;
    }
}

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

...
class Dinner
{
   ...
    public function cookFood()
    {
        $gas = new Gas();
        $gas->fire();
    }
   ...
}

为节省篇幅,以上代码使用了 ‘…’ 来隐藏了部分代码,以下文章情况类似。那么调用过程是这样的:

$dinner = new \Ioc\Dinner();
$dinner->cookFood();

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

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

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

工厂模式

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

Agent 类如下:

<?php
namespace Ioc;

class Agent
{
    public static function useEnergy()
    {
        return new Gas();
    }
}

Dinner 类如下:

...
class Dinner
{
    protected $energy;
   ...
    public function cookFood()
    {
        $this->energy = Agent::useEnergy();
        $this->energy->fire();
    }
   ...
}

如此,即可使 Dinner 不再直接依赖 Gas,而交由一个代理 Agent 来控制 energy 的创建。然而,Gas 依赖解除了,又带来了 Agent 的依赖,虽然 Agent 的更改可能性不太,但谁能保证呢。

依赖注入 (DI)

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

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

...
   public function setEnergy($energy)
   {
       $this->energy = $energy;
   }

   public function cookFood()
   {
       $this->energy->fire();
   }
...

添加一个 setEnergy 方法来注入依赖的对象。那么调用过程将变成:

$dinner = new \Ioc\Dinner();
$dinner->setEnergy(\Ioc\Agent::useEnergy());
$dinner->cookFood();

以上就是一种依赖注入的示例。Dinner 彻底解除了对能源类的依赖。

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

$dinner->setEnergy(...);
$dinner->setKitchen(...);
$dinner->setSauce(...);
$dinner->cookFood();

每次都要调用很多 set 方法,这样就更不科学了。与其这样,干脆所有 set 方法都交给一个 TopAgent 做好了。

TopAgent 类如下:

<?php
namespace Ioc;

class TopAgent
{
    public static function setAllDi()
    {
        $dinner = new Dinner();
        $dinner->setEnergy(Agent::useEnergy());
        $dinner->setKitchen(Agent::useKitchen());
        $dinner->setSauce(Agent::useSauce());

        return $dinner;
    }
}

这样,调用过程就变得简单了。

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

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

IOC 容器

IOC 容器首先是一种类注册器,其次它是一种更高级的依赖注入方式。它和工厂 Factory 其实性质一样,代理类,但实现机制不一样。

IOC 容器的 设计模式 叫做注册器模式。

Container 类如下:

<?php
namespace Ioc;

class Container
{
    protected static $objects = [];

    public static function set($key, $object)
    {
        self::$objects[$key] = $object;
    }

    public static function get($key){
        $closure = self::$objects[$key];
        return $closure();
    }
}

Agent 类再添加两个方法:

...
   public static function bindContainer()
   {
       return new Container();
   }

   public static function bindDinner(Container $container)
   {
       return new Dinner($container);
   }
...

Dinner 类接受一个 Container 注入:

<?php
namespace Ioc;

class Dinner
{
    protected $container;

    public function __construct(Container $container){
        $this->container = $container;
    }

    public function buyFood()
    {
        //
    }

    public function cookFood()
    {
        $this->container->get('energy')->fire();
    }

    public function eatFood()
    {
        //
    }
}

于是,调用过程便可漂亮的写成:

\Ioc\Container::set('energy',  function () {
    return \Ioc\Agent::useEnergy();
});

$dinner = \Ioc\Agent::bindDinner(\Ioc\Agent::bindContainer());
$dinner->cookFood();

将容器 Container 注入到 Dinner 。并实现了所有类的完全解耦。


以上所述就是小编给大家介绍的《软件工程入门-轻松理解依赖注入 (DI) 和 IoC 容器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Wireshark网络分析就这么简单

Wireshark网络分析就这么简单

林沛满 / 人民邮电出版社 / 2014-11-6 / 39.00

Wireshark可能是世界上最好的开源网络包分析器,能在多种平台上(比如Windows、Linux和Mac)抓取和分析网络包,在IT业界有着广泛的应用。 《Wireshark网络分析就这么简单》采用诙谐风趣的手法,由浅入深地用Wireshark分析了常见的网络协议,读者在学习Wireshark的同时,也会在不知不觉中理解这些协议。作者还通过身边发生的一些真实案例,分享了Wireshark的......一起来看看 《Wireshark网络分析就这么简单》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换