zoukankan      html  css  js  c++  java
  • laravel5.5的服务容器分析

    简介

    服务容器是Laravel的核心。见名知意,服务容器就是一个存放服务的地方,当我们需要某个服务的时候,我们就可以从这个容器中取出我们需要的服务。用更专业一点的术语来说,官网定义服务容器是这样的:

    Laravel服务容器是一个用于管理类依赖和执行依赖注入的强大工具。
    

    说到管理类依赖和执行依赖注入,搞过Spring开发的朋友,应该都熟悉,这些概念在这些年特别火。因为好用,所以火。现在这把火烧到了PHP这里。而说到这里,我们又不得不说两个特别重要的概念:IoC(控制反转)与DI(依赖注入)。

    IoC(控制反转)与DI(依赖注入)

    概念

    控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。通过控制反转,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

    简单一下:

    控制反转:容器(Container)控制应用程序(Application),由容器反向的向应用程序注入应用程序所需要的外部资源(组件类);
    依赖注入:应用程序(Application)依赖容器(Container)创建并注入它所需要的外部资源(组件类)。
    

    控制反转是用来进行对象解耦。通过借助第三方,将具有依赖关系的对象进行解耦,而这个第三方就是IoC容器。

    图解

    比如全体齿轮的转动由一个对象来控制,如类B。如下图所示:

    引入了IoC容器后,对象A、B、C、D之间没有了依赖关系,全体齿轮转动的控制权交给IoC容器。这时候齿轮转动控制权不属于任何对象,而属于IoC容器,所以控制权反转了,从某个对象转到了IoC容器。

    代码演示

    对于依赖注入,首先依赖就是指一种关系,如果在类A中创建了类B的实例,我们就可以说类A依赖类B。例如:

    <?php
    class B {
        public function func() {
            print_r(__CLASS__);
        }
    }
    
    class A {
        private $b;
    
        public function A() {
            $this->b = new B();
        }
    
        public function func() {
            $this->b->func();
        }
    }
    

    解耦方案:

    class A {
        private $b;
    
        public function A(B $bObj) {
            $this->b = $bObj;
        }
    
        public function func() {
            $this->b->func();
        }
    }
    

    将B对象实例作为类A的构造器参数进行传入,在调用类A构造器之前,类B实例已经被初始化好了。像这种非自己主动初始化依赖,而通过外部传入依赖对象的方式,就很完美的解决了上述的问题,这就是依赖注入。

    IoC容器详细解释

    以超人打怪兽的故事

    1.第一阶段

    我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,但是目前我们先大致定义一个只有属性的“超能力”:

    class Power {
        /**
         * 能力值
         */
        protected $ability;
     
        /**
         * 能力范围或距离
         */
        protected $range;
     
        public function __construct($ability, $range)
        {
            $this->ability = $ability;
            $this->range = $range;
        }
    }
    class Superman
    {
        protected $power;
     
        public function __construct()
        {
            $this->power = new Power(999, 100);
        }
    }
    

    这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,“超人”和“超能力”之间不可避免的产生了一个依赖。

    我们知道,超人的超能力是多元化的。我们现在进行修改,我们假设超人可以有以下多种超能力:
    飞行,属性有:飞行速度、持续飞行时间
    蛮力,属性有:力量值
    能量弹,属性有:伤害值、射击距离、同时射击个数
    我们创建了如下类:

    class Flight
    {
        protected $speed;
        protected $holdtime;
        public function __construct($speed, $holdtime) {}
    }
     
    class Force
    {
        protected $force;
        public function __construct($force) {}
    }
     
    class Shot
    {
        protected $atk;
        protected $range;
        protected $limit;
        public function __construct($atk, $range, $limit) {}
    }
    

    在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:

    class Superman
    {
        protected $power;
     
        public function __construct()
        {
    
            $this->power = new Fight(9, 100);
            // $this->power = new Force(45);
            // $this->power = new Shot(99, 50, 2);
            /*
            $this->power = array(
                new Force(45),
                new Shot(99, 50, 2)
            );
            */
        }
    }
    

    我们需要自己手动的在构造函数内实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力或者需要 变更 超能力的方法,我们必须 重新改造 超人。

    2.第二阶段(工厂模式)

    我们不应该手动在 “超人” 类中固化了他的 “超能力” 初始化的行为,而转由外部负责,由外部创造超能力模组、装置或者芯片等(我们后面统一称为 “模组”),植入超人体内的某一个接口,只要这个 “模组” 满足这个接口的装置都可以被超人所利用。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。

    工厂模式,就是一个类所以依赖的外部事物的实例,都可以被一个或多个 “工厂” 创建的这样一种开发模式。

    我们为了给超人制造超能力模组,我们创建了一个工厂,它可以制造各种各样的模组,且仅需要通过一个方法:

    class SuperModuleFactory
    {
        public function makeModule($moduleName, $options)
        {
            switch ($moduleName) {
                case 'Fight':     return new Fight($options[0], $options[1]);
                case 'Force':     return new Force($options[0]);
                case 'Shot':     return new Shot($options[0], $options[1], $options[2]);
            }
        }
    }
    class Superman
    {
        protected $power;
     
        public function __construct()
        {
            // 初始化工厂
            $factory = new SuperModuleFactory;
     
            // 通过工厂提供的方法制造需要的模块
            $this->power = $factory->makeModule('Fight', [9, 100]);
            // $this->power = $factory->makeModule('Force', [45]);
            // $this->power = $factory->makeModule('Shot', [99, 50, 2]);
            /*
            $this->power = array(
                $factory->makeModule('Force', [45]),
                $factory->makeModule('Shot', [99, 50, 2])
            );
            */
        }
    }
    

    可以看得出,我们不再需要在超人初始化之初,去初始化许多第三方类,只需初始化一个工厂类,即可满足需求。但这样似乎和以前区别不大,只是没有那么多 new 关键字。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。

    class Superman
    {
        protected $power;
     
        public function __construct(array $modules)
        {
            // 初始化工厂
            $factory = new SuperModuleFactory;
     
            // 通过工厂提供的方法制造需要的模块
            foreach ($modules as $moduleName => $moduleOptions) {
                $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
            }
        }
    }
    // 创建超人
    $superman = new Superman([
    		'Fight' => [9, 100], 
    		'Shot' => [99, 50, 2]
        ]);
    

    现在修改的结果令人满意。现在,“超人” 的创建不再依赖任何一个 “超能力” 的类,我们如若修改了或者增加了新的超能力,只需要针对修改 SuperModuleFactory 即可。但是,这才刚刚开始。

    第三阶段(依赖注入)

    由 “超人” 对 “超能力” 的依赖变成 “超人” 对 “超能力模组工厂” 的依赖后,对付小怪兽们变得更加得心应手。但这也正如你所看到的,依赖并未解除,只是由原来对多个外部的依赖变成了对一个 “工厂” 的依赖。假如工厂出了点麻烦,问题变得就很棘手。

    其实大多数情况下,工厂模式已经足够了。工厂模式的缺点就是:接口未知(即没有一个很好的契约模型)、产生对象类型单一。总之就是,还是不够灵活。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。

    我们知道,超人依赖的模组,我们要求有统一的接口,这样才能和超人身上的注入接口对接。事实上,不仅仅只有一堆小怪兽,还有更多的大怪兽。这时候似乎工厂的生产能力显得有些不足 —— 由于工厂模式下,所有的模组都已经在工厂类中安排好了,如果有新的、高级的模组加入,我们必须修改工厂类(好比增加新的生产线):

    class SuperModuleFactory
    {
        public function makeModule($moduleName, $options)
        {
            switch ($moduleName) {
                case 'Fight':     return new Fight($options[0], $options[1]);
                case 'Force':     return new Force($options[0]);
                case 'Shot':     return new Shot($options[0], $options[1], $options[2]);
                // case 'more': .......
                // case 'and more': .......
                // case 'and more': .......
                // case 'oh no! its too many!': .......
            }
        }
    }
    

    看到没。。。噩梦般的感受!

    下一步就是我们今天的主要配角 —— DI (依赖注入)

    由于对超能力模组的需求不断增大,我们需要集合整个世界的高智商人才一起解决问题,不应该仅仅只有几个工厂垄断负责。不过高智商人才们创造出的超能力模组没有统一的接口,自然而然无法被正常使用。这时我们需要提出一种契约,这样无论是谁创造出的模组,都符合这样的接口,自然就可被正常使用。

    interface SuperModuleInterface
    {
        /**
         * 超能力激活方法
         *
         * 任何一个超能力都得有该方法,并拥有一个参数
         *@param array $target 针对目标,可以是一个或多个,自己或他人
         */
        public function activate(array $target);
    }
    

    上文中,我们定下了一个接口 (超能力模组的规范、契约),所有被创造的模组必须遵守该规范,才能被生产。
    这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类:

    /**
     * X-超能量
     */
    class XPower implements SuperModuleInterface
    {
        public function activate(array $target)
        {
            // 这只是个例子。。具体自行脑补
        }
    }
     
    /**
     * 终极炸弹
     */
    class UltraBomb implements SuperModuleInterface
    {
        public function activate(array $target)
        {
            // 这只是个例子。。具体自行脑补
        }
    }
    
    class Superman
    {
        protected $module;
     
        public function __construct(SuperModuleInterface $module)
        {
            $this->module = $module
        }
    }
    

    改造完毕!现在,当我们初始化 “超人” 类的时候,提供的模组实例必须是一个 SuperModuleInterface 接口的实现。
    正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。这样的话,虽然一个超人只有一个超能力,但超人更容易变多,我们也不怕怪兽啦!

    现在有人疑惑了,你要讲的 依赖注入 呢?其实,上面讲的内容,正是依赖注入。

    什么叫做 依赖注入?
    本文从开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于 依赖注入(DI) 。下面就是一个典型的依赖注入:

    // 超能力模组
    $superModule = new XPower;
    
    // 初始化一个超人,并注入一个超能力模组依赖
    $superMan = new Superman($superModule);
    

    关于依赖注入这个本文的主要配角,也就这么多需要讲的。理解了依赖注入,我们就可以继续深入问题。慢慢走近今天的主角……

    第四阶段 (IoC容器)

    刚刚列了一段代码:

    $superModule = new XPower; 
    $superMan = new Superman($superModule);
    

    手动的创建了一个超能力模组、手动的创建超人并注入了刚刚创建超能力模组。
    一群怪兽来了,如此低效率产出超人是不现实,我们需要自动化。我们需要一种高级的生产车间,我们只需要向生产车间提交一个脚本,工厂便能够通过指令自动化生产。这种更为高级的工厂,就是工厂模式的升华 —— 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);
        }
    }
    

    这时候,一个十分粗糙的容器就诞生了。现在的确很简陋,但不妨碍我们进一步提升他。先着眼现在,看看这个容器如何使用吧!

    // 创建一个容器(后面称作超级工厂)
    $container = new Container;
     
    // 向该 超级工厂 添加 超人 的生产脚本
    $container->bind('superman', function($container, $moduleName) {
        return new Superman($container->make($moduleName));
    });
     
    // 向该 超级工厂 添加 超能力模组 的生产脚本
    $container->bind('xpower', function($container) {
        return new XPower;
    });
     
    // 同上
    $container->bind('ultrabomb', function($container) {
        return new UltraBomb;
    });
     
    // ******************  华丽的分割线  **********************
    // 开始启动生产
    $superman_1 = $container->make('superman', 'xpower');
    $superman_2 = $container->make('superman', 'ultrabomb');
    $superman_3 = $container->make('superman', 'xpower');
    // ...随意添加
    

    通过最初的 绑定(bind) 操作,我们向 超级工厂 注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。我们彻底的解除了 超人 与 超能力模组 的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的 脚本 ,只有在真正的 生产(make) 操作被调用执行时,才会触发。

    实际上,真正的 IoC 容器更为高级。我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,

    参考文章

    laravel 学习笔记 —— 神奇的服务容器
    https://www.insp.top/learn-laravel-container
    
    Laravel初级教程之服务容器 
    https://www.jellythink.com/archives/383
    
    laravel中文文档  
    https://learnku.com/docs/laravel/5.7/lifecycle/2248
  • 相关阅读:
    shell 模式匹配:case
    知识目录总结
    【运维--系统】nacos介绍和安装
    【运维--监控】zabbix自定义发现策略
    【转】在CentOS 8 / RHEL 8上配置主/从BIND DNS服务器
    【运维--安全相关】cerbot证书自动化续期
    【转】Docker 核心技术与实现原理
    【转】Kubernetes scheduler学习笔记
    [转]自建CDN防御DDoS
    【转】Dockerfile
  • 原文地址:https://www.cnblogs.com/sentangle/p/11677506.html
Copyright © 2011-2022 走看看