一文读懂什么是依赖注入, Ioc容器

什么是依赖

打个比方,就好比每个人都需要穿衣服才能进行正常的社会活动,这就是说是个人都需要依赖衣服才能正常社会活动。
相应的,就是一个类需要另一个类才能完成工作,这就是依赖

code demo

class Person{
      protected $clothes;
      public function __construct() 
      {
          $this->clothes = new Clothes();
      }
}

看上面的代码就知道 人依赖了衣服,

什么是依赖注入呢,我们改造下上面的代码

class Person {
        protected $clothes;
        public function __construct(Clothes $clothes) {
            $this->clothes = $clothes;
        }
    }

    $person = new Person(new Clothes());

这里的Person 依赖注入了clothes类

理解了依赖注入,我们就可以接着去理解IOC

IOC

IOC 是什么呢,看明白了依赖注入(DI)后就很容易理解了
通过DI我们可以看到,一个类所需要的依赖类是由我们主动实例化后传入类中的。
控制反转的意思就是说将依赖类的控制权交出去,由主动变被动。

laravel 代码

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{

    public function login(Request $request)
    {
        //这就是IOC,我们不需要主动传入类了一切由laravel去实现
    }
}

看到这你可能有疑问了,这是怎么实现的呢?
这就是靠服务容器了,请往下接着看。

服务容器

看了很多文章,我一致认为服务容器就是一种设计模式。

它的目的就是解耦依赖。

它有点类似于我前面说的《享元模式》。区别在于服务容器解决了所有依赖的实现。

这里我们再从头至尾的看一遍,怎么一步步演化出服务容器。

依然是人的例子,我们知道人依赖衣服鞋子裤子,可是衣服鞋子也有很多种品牌呀。

先看一个最原始的代码例子:

 class Person {
        protected $clothes;

        public function __construct($type = null) {

            switch($type) {
                case 'anta':
                    $this->clothes = new Anta();
                case 'adidas':
                    $this->clothes = new Adidas();
                default:
                    $this->clothes = new 360();
            }
        }
    }

或许你一眼就看出了问题在哪。

如果我们又要增加一种衣服,那我们又得对这个类进行修改。这样下去,这个类会变得庞大且耦合程度过高。

那么我们可以怎么修改呢?

工厂模式

这样我们可以避免直接的修改 Person 类。


    class Factory {

        public static function getInstance($type){
            switch($type) {
                case 'anta':
                    $this->clothes = new Anta();
                    break;
                case 'adidas':
                    $this->clothes = new Adidas();
                    break;
                default:
                    $this->clothes = new 360();
                    break;
            }
        }
    }

    class Person {
        protected $clothes;
        public function __construct($type == null) {
            $this->clothes = Factory::getInstance($type);
        }
    }

这样使用简单工厂模式后,我们后续的修改可以不用对 Person 类进行操作而只要修改工厂类就行了。这就相当于对 Person 类进行了解耦。

Person 类虽不在依赖那些衣服类了,但是却变为依赖工厂类了。

后续添加新类型的衣服就必须对工厂类进行修改。

所以这个工厂类还不能很好的满足要求,我们知道人对衣服的接口都是一致的,衣服必须实现这一接口才能被人识别,那我们对 Person 和 Clothes 类进行修改。

DI(依赖注入)

interface Clothes {
        public function type();
    }

    class AntaClothes implements Clothes {
        public function type(){
            echo '安踏';
        }
    }

    class AdidasClothes implements Clothes {
        public function type(){
            echo 'adidas';
        }
    }

    class Person {
        protected $clothes;

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

    $person = new Person(new AntaClothes());

可是这样也有问题,如果我们后续有人使用的衣服不满意要进行替换呢? 我们又回到原点了,必须去修改传入的衣服类。

能不能做成可配置的呢?

IOC服务容器(超级工厂)

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

这就是一个简单的 IOC 服务容器。
这个怎么解决我们上述的问题呢?

 $container = new Container;

 $container->bind('Clothes', function($container){
     return new AntaClothes;
 });

 $container->bind('Person',function($container,$module){
     return new Person($container->make($module));
 });

 $person = $container->make('Person',['Clothes']);

这里生产出来的 Person 类就是一个使用了安踏衣服的类了。

如果我们要换衣服怎么办呢?

 $container->bind('Clothes', function($container){
     return new AdidasClothes;
 });

 $container->bind('Person',function($container,$module){
     return new Person($container->make($module));
 });

 $person = $container->make('Person', ['Clothes']);

只要对 bind 绑定的 Clothes 类的实现进行修改,我们就可以很容换掉衣服了。这就是一个服务容器。

对服务容器进行一个理解:

容器就是一个装东西的,好比碗。而服务就是这个碗要装的饭呀,菜呀,等等东西。当我们需要饭时,我们就能从这个碗里拿到。如果你想在饭里加点菜(也就是饭依赖注入了菜),我们从碗里直接拿饭就可以了,而这些依赖都由容器解决了(这也就是控制反转)。

我们需要做的就是对提供的服务进行维护。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2
小黄

看成了 “一秒懂什么是依赖注入, Ioc容器” 尴尬了- -!

2年前 评论

Person 类虽不在依赖那些键盘类了,但是却变为依赖工厂类了。

这个键盘类是什么?

2年前 评论
ChenAfrica (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!