【看完就懂】Laravel 服务容器,IoC,DI
82

DI

DI就是常说的依赖注入,那么究竟什么是依赖注入呢?

打个比方,电脑(非笔记本哈)需要键盘和鼠标我们才能进行操作,这个‘需要’换句话说就是‘依赖’键盘和鼠标。

那么,相应的,一个类需要另一个类才能进行作业,那么这也就是依赖。

看一段代码:


    class Computer {
        protected $keyboard;

        public function __construct() {
            $this->$keyboard = new Keyboard();
        }
    }

    这里的Computer类依赖了键盘类。

好,既然我们已经知道了什么是依赖,那么什么是注入呢?

我们改造一下上面的代码:

     class Computer {
        protected $keyboard;

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

    $computer = new Computer(new Keyboard());

    这里的Computer类依赖注入了Keyboard类。

关于依赖注入,我的理解是:

所需要的类通过参数的形式传入的就是依赖注入。

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

IOC

IOC是什么呢?

中文叫控制反转。啥意思呢? 这个看明白了DI后就能很容易的理解了。

通过DI我们可以看到,一个类所需要的依赖类是由我们主动实例化后传入类中的。

控制反转和这个有什么关系呢?

控制反转意思是说将依赖类的控制权交出去,由主动变为被动。

看一段laravel代码:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SessionController extends Controller
{

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

看到这你可能有疑问了,这是怎么实现的呢?

这就是靠服务容器了,请往下接着看。

服务容器

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

它的目的就是解耦依赖。

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

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

依然是电脑的例子,我们知道电脑依赖键盘鼠标,可是键盘鼠标也有很多种呀。

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


     class Computer {
        protected $keyboard;

        public function __construct($type == null) {

            switch($type) {
                case 'common':
                    $this->keyboard = new CommonKeyboard();
                    break;
                case 'awesome':
                    $this->keyboard = new AweSomeKeyboard();
                    break;
                default:
                    $this->keyboard = new Keyboard();
                    break;
            }

        }
    }

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

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

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

  • 工厂模式

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

    简单工厂
    class Factory {

        public static function getInstance($type){
            switch($type) {
                case 'common':
                    $this->keyboard = new CommonKeyboard();
                    break;
                case 'awesome':
                    $this->keyboard = new AweSomeKeyboard();
                    break;
                default:
                    $this->keyboard = new Keyboard();
                    break;
            }
        }
    }

    class Computer {
        protected $keyboard;

        public function __construct($type == null) {
            $this->keyboard = Factory::getInstance($type);
        }
    }

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

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

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

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

  • DI(依赖注入)
    interface Board {
        public function type();
    }

    class CommonBoard implements Board {
        public function type(){
            echo '普通键盘';
        }
    }

    class MechanicalKeyboard implements Board {
        public function type(){
            echo '机械键盘';
        }
    }

    class Computer {
        protected $keyboard;

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

    $computer = new Computer(new MechanialKeyBoard());

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

能不能做成可配置的呢?

  • 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('Board', function($container){
        return new CommonBoard;
    });

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

    $computer = $container->make('Computer',['Board']);

这里生产出来的Computer类就是一个使用普通键盘的电脑类了。

解释一下代码:

    bind(name,function($container){
        return new Name;
    })

    这里的name和Name之间的关系是:
    当我需要name类的时候你就给我实例化Name类。

    make(name)方法是对name进行生产返回一个实例。

如果我们要更换键盘怎么办呢?

    $container->bind('Board', function($container){
        return new MechanicalBoard;
    });

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

    $computer = $container->make('Computer',['Board']);

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

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

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

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

我们看一段真实的在laravel框架上能跑的代码:

代码

当然laravel框架的服务容器比这里的要复杂很多了,但我们明白了它的使用目的以及使用场景就不难去入手laravel了。

PS: 个人微信公众号'涂晓伟',关注送laravel,linux,nginx等学习资料!!!

PS: 知乎专栏 '程序边缘',热烈欢迎!!!

三海

本帖由系统于 1周前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 24
雪风

我就喜欢这种深入浅出的文章

2周前
simpleT

@雪风 恭喜首评:smile:,献上香吻一个:kissing:

2周前
wanghan

要是还没看懂怎么办,还有的救吗

1周前

依赖注入一般是靠PHP的反射类来实现的,可以实现无限级依赖的注入。

1周前
BradStevens

非常棒

1周前
不温柔

file

1周前
simpleT

@不温柔 谢谢指正。hah

1周前
simpleT

@tsin 是的

1周前
simpleT

@BradStevens :smile:

1周前
simpleT

@wanghan 多敲代码:smile:

1周前

https://implode.io/4mT8O4 这个网站不错,还能直接运行laravel 代码

1周前

$container = new $container;
是否应为
$container = new Container;

1周前
chanjsq

@lovecn 谢安利

1周前
simpleT

@echofree313 是的,写错了:smile_cat:

1周前

$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});
求问:$module参数 指什么。
看不太懂这一步。求解释。

1周前
simpleT

@echofree313 所依赖的模块

1周前

我还是有点不理解。哈哈。好像又发现了一个错误的地方。
$computer = $container->make('Computer','Board');
应为
$computer = $container->make('Computer',['Board']);
私信楼主了。怎么添加个联系方式,求问下。

1周前
simpleT

@echofree313 you are right

1周前
simpleT

@echofree313 这里只要理解为什么要有服务容器以及它的应用场景就好了,技术细节还要参考其他书籍

1周前

其实 我是不理解 绑定 computer。绑定键盘类是理解的。绑定computer时候,里面的$container->make($module),不理解运行过程,可以给个具体的过程分析下么。这个貌似运行不成功吧?有点菜鸡。求指教。

1周前
simpleT

@echofree313 可以运行的,用debug工具对每一步打下断点就可以明白运行流程了。

1周前

楼主,服务容器的基本原理知道了,但在 Laravel 中怎么具体应用的,能举个例子吗?
比如,控制器中的非构造方法是怎么实现自动注入的,如果能有一篇文分析下完整过程,那就太棒了。

class SessionController extends Controller
{

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

@hehorange 好的,争取在年前写一篇相关的文章:smile:

6天前

@simpleT ↖(^ω^)↗期待,先点赞了

5天前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!