理解「依赖注入」和「控制反转」


June 5, 2017 · 思维 · 104次阅读

一. 依赖注入和控制反转是什么?

名词解释:

IoC

  • 控制反转 Inversion of Control
  • 依赖关系的转移
  • 依赖抽象而非实践

DI

  • 依赖注入 Dependency Injection
  • 不必自己在代码中维护对象的依赖
  • 容器自动根据配置,将依赖注入指定对象

Container

  • 管理对象的生成、资源取得、销毁等生命周期
  • 建立对象与对象之间的依赖关系
  • 启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系

------------以上引用自网络

二. IoC代码演示

高层应用程序需要调用底层模块接口,导致高层应用程序对低层模块产生依赖。

<?php
    // 高层类
    class Order
    {
        private $track;
        public function __construct()
        {
            $this->track = new sf();    //顺丰快递
        }

        public function query()
        {
            return $this->track->select();
        }
    }
    // 底层类,快递接口
    class sf
    {
        public function select
        {
            return "快递已送达";
        }
    }
    $order = new Order();
    $order->query();

假设我们有一个订单类,需要跟踪订单的物流信息,物流目前是和顺丰快递进行合作。但后因种种原因,需要迁移快递物流平台到圆通上去,那现在因为产生了物流接口依赖,程序无法重用,并且必须要修改或者新增圆通快递接口。这就是由于低层模块变化导致高层也跟着变化,不好的设计。

而正如上面的IoC解释

  • 控制反转 Inversion of Control
  • 依赖关系的转移
  • 依赖抽象而非实践

程序不应该依赖于具体的实现,而是要依赖抽象的接口。如下:

<?php
    // 物流平台接口
    interface trackBox
    {
        public function select();
    }

    // 高层类
    class Order
    {
        private $track;

        public function setTrack($track)
        {
            $this->track = $track;
        }

        public function query()
        {
            return $this->track->select();
        }
    }

    // 底层 顺丰
    class sf implements trackBox
    {
        public function select()
        {
            return "顺丰快递已送达";
        }
    }

    // 底层 圆通
    class yt implements trackBox
    {
        public function select()
        {
            return "圆通快递已送达";
        }
    }

    $order = new Order();
    $order->setTrack(new sf());
    $order->query(): //顺丰快递已送达

    $order->setTrack(new yt());
    $order->query(): //圆通快递已送达

这样就将原来的物流查询模块对程序的控制权转移到trackBox接口上,让order类和各种物流接口类都依赖于trackBox,这样就实现了控制反转。面对变化,高层不用修改一行代码,不再依赖低层,而是依赖注入,这也就引出了(依赖注入)DI。<br/>
依赖注入的方式有很多,但是主要思想就是如上所示,我们将一个依赖通过注入的方式进行使用。而不是直接将依赖写入被依赖中进行具体的实现。

三. 依赖注入容器 Dependency Injection Container

那我们接下来继续思考,如果此后业务扩张,合作的物流平台越来越丰富,那么高层程序依赖的物流接口就会越来越多,我们就需要创建的依赖接口实例也会越来越多,这样就很不利于后期的维护

    //底层物流接口
    class sf implements trackBox{ ... } //顺丰
    class yt implements trackBox{ ... } //圆通
    class zt implements trackBox{ ... } //中通
    class yd implements trackBox{ ... } //韵达
    class ems implements trackBox{ ... } //EMS

    //依赖注入
    $order = new Order();
    $order->setTrack(new sf());
    $order->select(): //顺丰快递已送达

    $order->setTrack(new yt());
    $order->select(): //圆通快递已送达

    $order->setTrack(new zt());
    $order->select(): //中通快递已送达

    $order->setTrack(new yd());
    $order->select(): //韵达快递已送达

    $order->setTrack(new ems());
    $order->select(): //EMS快递已送达

这个时候,我们有一种比较优雅的设计方案就是采用"工厂模式",

工厂模式,个人理解就是一个类所依赖的外部实例,可以被一个或多个"工厂"创建的这样一种开发模式

我们不手动进行创建依赖实例,而是通过工厂去创建依赖实例,我们需要做的只是通过工厂拿到我们需要用到的依赖实例

<?php
    // 工厂类
    class TrackFactory
    {
        public function makeTrack($trackName)
        {
            switch ($trackName) {
                case 'sf':     return new sf();
                case 'yt':     return new yt();
                case 'zt':     return new zt();
                case 'yd':     return new yd();
                case 'ems':     return new ems();
            }

            // return new $trackName();
        }
    }

    // 高层类
    class Order
    {
        private $track;

        public function __construct(array $tracks)
        {
            // 初始化工厂类
            $trackFactory = new TrackFactory();
            // 通过工厂类提供的方法制造所需要的物流平台
            foreach ($tracks as $trackName) {
                $this->$track[] = $trackFactory->makeTrack($trackName);
            }
        }

        public function query($trackName)
        {
            return $this->track[$trackName]->select();
        }
    }

    $Order = new Order(['sf','yd','ems','zt',]);
    $Order->query('sf');
    $Order->query('yd');
    $Order->query('ems');

尽管工厂模式让我们的代码变得优雅了一点,但是我们又回到了开头的错误中来,我们现在对工厂类进行了依赖,并且在物流接口愈加丰富后,工厂类也会慢慢变的冗长而不利于维护,那我们要怎么才能设计出一套即优雅又松耦合的应用程序呢?这个时候就需要有容器这个概念

Container

  • 管理对象的生成、资源取得、销毁等生命周期
  • 建立对象与对象之间的依赖关系
  • 启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系
<?php
    // 容器类
    class Container
    {
        protected $binds;
        public function bind($service, $instance)
        {
            if ($concrete instanceof Closure) {
                $this->binds[$service] = $instance;
            }
        }

        public function make($service, $parameters)
        {
            return call_user_func_array([$this->binds[$service],], $parameters);
        }
    }

    // 高层类
    class Order
    {
        private $track;

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

        public function query()
        {
            return $this->track->select();
        }
    }

    $container = new Container();
    $container->bind('sf', function($container) {
        return new sf();
    });
    $container->bind('Order', function($container) {
        return new Order();
    });

    $Order = $container->make('Order','sf');

标签:设计模式,PHP,原创

最后编辑于:2019/11/26 05:47

添加新评论

控制面板