我对Laravel服务容器的一些理解

服务容器,又叫IOC容器,其中的核心思想就是控制反转(Inversion Of Control,简称IOC),实现控制反转主要有依赖注入(Dependency Injection,简称DI)和依赖寻找(Dependency Lookup)。

一、什么是控制反转
控制反转简单的说就是指通过外部去负责一个类的所有依赖

这么说可能比较抽象,下面我们来谈谈它的由来

很久之前,我们是这样做的。
假设我们现在有一个Computer类和一个Mouse类

//鼠标类
class Mouse
{
    private $senstion;
    //鼠标的灵敏度
    public function __construct($senstion = 40)
    {
        $this->senstion = $senstion;
    }
    public function name()
    {
        echo "我是鼠标,我的灵敏度是{$this->senstion}";
    }
}
//计算机类
class Computer
{
    public $mouse;
    public $keyBoard;
    public function __construct()
    {
        $this->mouse = new Mouse(20);
    }
}
$computer = new Computer();

可以看到,我们在实例化Computer类的时候,同时也实例化了Mouse类。

假设我们现在要改造鼠标类(比如给它增加一个属性,并且在生成实例的时候需要传值)这个时候,我们不仅要修改鼠标类,同时还要修改计算机类中的new Mouse,这样做效率很低。假设我们要依赖的类很多,修改起来会很麻烦

我们可以看到这样做的缺点,那就是Mouse类和Computer类之间的耦合度很高。而软件设计过程中我们讲究的是低耦合,高内聚。所以,这样并不是我们所希望的。

那么,怎样做可以降低Computer类和Mouse类之间的耦合呢?
这时候人们就想,我把生产Mouse的活交给别人去做,我只要拿生产好的东西就行了。没错,事实上我们也是这样做的。这时候,我们的工厂就登场了。接上面的代码,我们新建一个工厂类Factory

//工厂类
class Factory
{
    public function make($module, $parames = [])
    {
        return new $module(...$parames);
    }
}

好了,我们的一个简单的工厂就建好了,我们告诉工厂我们要造什么东西,工厂负责去生产我们的Computer所需要的东西,比如Mouse。同时,我们也要改造一下Computer类来利用工厂来帮我们生产我们需要的东西。我们可以利用工厂进行批量生产。(一个依赖可能看不大出工厂的好处,你可以想象我们的依赖很多)

class Computer
{
    public $modules = [];
    public function __construct($modules)
    {
       $factory = new Factory();
       foreach ($modules as $key => $value) {
           $this->modules[$key] = $factory->make($key, $value);
       }
    }
}

$computer = new Computer([
    'Mouse' => [40] ,
]);

怎么样,我们的工厂很给力吧,只要我们的工厂没变,就算零件有新的变化,我们也不在需要去改造Computer。

可以看到,我们对Mouse的依赖变成了对工厂Factory的依赖,假设Factory进行了重新改造,我们依然需要改造Computer类。他们之间还是存在较高耦合

那么,有什么方法可以帮助我们解决这种高耦合的依赖呢?
答案就是控制反转

二、依赖注入
依赖注入是实现控制反转的一种方式。依赖注入就是通过外部以参数或者其他形式注入到另一个实例中就叫依赖注入。

注意概念中的“外部”二字。前面我们所说的都是在构造函数内部new一个新类,或者在构造函数中通过工厂去获得这些类的实例,都是在生成依赖对象实例的时候去生成被依赖对象的实例。拿例子来说就是我们只有在生成Computer实例的时候,才会去生Mouse的实例。而现在不同了,我们是先生成好Mouse实例,再把这个实例当成参数传过去,类似于这样:

class Computer
{
    public $modules;
    public function __construct(Mouse $module)
    {
        $this->module = $module;
    }
}
//先在外部生成被依赖类的实例
$mouse = new Mouse(50);
//再注入依赖
$computer = new Computer($mouse);

为了进一步降低他们之间的耦合,我们设计一个接口,同时我们的Mouse类继承这个接口。


Interface Component
{
    public function name();
}

class Mouse implements Component
{
    private $senstion;
    //鼠标的灵敏度
    public function __construct($senstion = 40)
    {
        $this->senstion = $senstion;
    }
    public function name()
    {
        echo "我是鼠标,我的灵敏度是{$this->senstion}";
    }
}

同时我们改造我们的Computer类


class Computer
{
    public $modules;
    public function __construct(Component $module)
    {
        $this->module = $module;
    }
}

这样,我们把Computer类改造成从对具体类的依赖变成对接口的依赖。只要是符合这个接口的零件我们都可以注入到Computer类中。这样,就完全解决了他们之间的耦合度。

但是,这种生产方式效率太低,假如我们的类依赖多个类,那么我们就要事先手动去new这几个对象,再注入依赖。人都是懒惰的,我们最想的就是,通过一条指令就可以帮我们搞定所有事情。
这就是我们要介绍的IOC容器,也就是服务容器(一个更为高级的工厂)。
我们向服务容器提供一个的生产脚本(这一步也叫注册服务),然后容器就会保存我们的生产脚本。我们要用的时候,我们通过指令告诉容器我们要执行那个脚本。
下面是我基于Laravel服务容器的代码写的一个简化版本的服务容器

class Container
{
    //用来存放服务
    public $binds = [];
    //当前的参数
    private $cur = [];
    //注册服务
    public function bind($name, $service)
    {
        $this->binds[$name] = $service;
    }
    //运行服务内容
    public function make($name, array $params = [])
    {
        //获取服务内容
        $service = $this->getService($name);
        //服务内容为闭包,直接执行
        if($service instanceof Closure) {
            array_unshift($params, $this);
            return call_user_func_array($service, $params);
        }
        //不是闭包则通过类的反射对象来解决类的依赖,生成对应的实例
        $rel = new ReflectionClass($service);
        //获取构造函数
        $construct = $rel->getConstructor();
        //没有构造函数则返回类的实例
        if(is_null($construct)) {
            return new $service();
        }
        $this->cur[] = $params;
        //获取构造函数的参数
        $deps = $construct->getParameters();
        //解决构造函数中的依赖
        $result = $this->resolvedDep($deps);
        array_pop($this->cur);
        //返回生成实例
        return $rel->newInstanceArgs($result);
    }
    //获取服务内容
    protected function getService($name)
    {
        return isset($this->binds[$name]) ? $this->binds[$name] : $name;
    }
    //解决依赖
    protected function resolvedDep($params)
    {
        $result = [];
        foreach ($params as $param) {
            if(array_key_exists($param->name, end($this->cur))) {
                $result[] = end($this->cur)[$param->name];
                continue;
            }
            if(is_null($param->getClass())) {
                if($param->isDefaultValueAvailable()) {
                    $result[] = $param->getDefaultValue();
                } else {
                    throw new Exception("参数错误", 1);
                }
            } else {
                $result[] = $this->make($param->getClass()->name);
            }
        }
        return $result;
    }
}

于是,有了容器后,我们就可以这样做

//创建容器
$app = new Container();
//注册服务
$app->bind('Mouse', function($app, ...$params) {
    return new Mouse($params[0]);
});
//注册服务
$app->bind('Computer', function($app, $module) {
    return new Computer($app->make($module, [50]));
});
//执行服务,生成类的实例
$computer = $app->make('Computer', ['Mouse']);

make方法第一个参数数服务名称,第二个是我们传递的参数(Array)
bind方法第一个参数是服务名称,第二个参数是服务的内容。
通常服务名称我们可以自定义。但是依赖注入情况下我们的名称必须是某个接口名或者类名,比如下面这样我们不是通过闭包来解决依赖注入的情况:

//创建容器
$app = new Container();
//绑定接口到具体实现
$app->bind('Component', 'Mouse');
//生成实例
$computer = $app->make('Computer');

上面的bind方法我们绑定了一个接口到它的具体实现,那么服务名称就应该是接口名,服务内容就是实现这个接口的类名。

ps:如果make方法里的服务名没有绑定过服务,相当于容器帮我们new了一个类。

此文参考IoC 容器 —— Laravel 的核心

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值