精华内容
下载资源
问答
  • 作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。 本文一大半...

    容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。

    IoC 容器, laravel 的核心#

    Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。

    本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。

    IoC 容器诞生的故事#

    讲解 IoC 容器有很多的文章,我之前也写过。但现在我打算利用当下的灵感重新来过,那么开始吧。

    超人和超能力,依赖的产生!#

    面向对象编程,有以下几样东西无时不刻的接触:接口还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。

    怪物横行的世界,总归需要点超级人物来摆平。

    我们把一个“超人”作为一个类,

    class Superman {}

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

    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) {}
    }

    *为了省事儿我没有详细写出 __construct() 这个构造函数的全部,只写了需要传递的参数。

    好了,这下我们的超人有点“忙”了。在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:

    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)
            );
            */
        }
    }

    我们需要自己手动的在构造函数内(或者其他方法里)实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力,或者需要 变更 超能力的方法,我们必须 重新改造 超人。换句话说就是,改变超能力的同时,我还得重新制造个超人。效率太低了!新超人还没创造完成世界早已被毁灭。

    这时,灵机一动的人想到:为什么不可以这样呢?超人的能力可以被随时更换,只需要添加或者更新一个芯片或者其他装置啥的(想到钢铁侠没)。这样的话就不要整个重新来过了。

    对,就是这样的。

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

    工厂模式,依赖转移!#

    当然,实现控制反转的方法有几种。在这之前,不如我们先了解一些好玩的东西。

    我们可以想到,组件、工具(或者超人的模组),是一种可被生产的玩意儿,生产的地方当然是 “工厂(Factory)”,于是有人就提出了这样一种模式: 工厂模式

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

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

    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 即可。扩充超能力的同时不再需要重新编辑超人的类文件,使得我们变得很轻松。但是,这才刚刚开始。

    再进一步!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]);
                // 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);
    }

    上文中,我们定下了一个接口 (超能力模组的规范、契约),所有被创造的模组必须遵守该规范,才能被生产。

    其实,这就是 php 中 接口( interface ) 的用处和意义!很多人觉得,为什么 php 需要接口这种东西?难道不是 java 、 C# 之类的语言才有的吗?这么说,只要是一个正常的面向对象编程语言(虽然 php 可以面向过程),都应该具备这一特性。因为一个 对象(object) 本身是由他的模板或者原型 —— 类 (class) ,经过实例化后产生的一个具体事物,而有时候,实现统一种方法且不同功能(或特性)的时候,会存在很多的类(class),这时候就需要有一个契约,让大家编写出可以被随时替换却不会产生影响的接口。这种由编程语言本身提出的硬性规范,会增加更多优秀的特性。

    虽然有些绕,但通过我们接下来的实例,大家会慢慢领会接口带来的好处。

    这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类:

    /**
     * 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 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。

    不过我告诉大家,这种自动搜寻依赖需求的功能,是通过 反射(Reflection) 实现的,恰好的,php 完美的支持反射机制!关于反射,php 官方文档有详细的资料,并且中文翻译基本覆盖,足够学习和研究!

    http://php.net/manual/zh/book.reflection.php

    现在,到目前为止,我们已经不再惧怕怪兽们了。高智商人才集思广益,井井有条,根据接口契约创造规范的超能力模组。超人开始批量产出。最终,人人都是超人,你也可以是哦 :stuck_out_tongue_closed_eyes:

    回归正常世界。我们开始重新审视 laravel 的核心。#

    现在,我们开始慢慢解读 laravel 的核心。其实,laravel 的核心就是一个 IoC 容器,也恰好是我之前所说的高级的 IoC 容器。

    可以说,laravel 的核心本身十分轻量,并没有什么很神奇很实质性的应用功能。很多人用到的各种功能模块比如 Route(路由)Eloquent ORM(数据库 ORM 组件)Request and Response(请求和响应)等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被你所使用,其实都是 laravel 的服务容器负责的。

    我们以大家最常见的 Route 类作为例子。大家可能经常见到路由定义是这样的:

    Route::get('/', function() {
        // bla bla bla...
    });

    实际上, Route 类被定义在这个命名空间:Illuminate\Routing\Router,文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php

    我们通过打开发现,这个类的这一系列方法,如 getpostany 等都不是静态(static)方法,这是怎么一回事儿?不要急,我们继续。

    服务提供者#

    我们在前文介绍 IoC 容器的部分中,提到了,一个类需要绑定、注册至容器中,才能被“制造”。

    对,一个类要被容器所能够提取,必须要先注册至这个容器。既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器,那么提供服务并绑定服务至容器的东西,就是 服务提供者(ServiceProvider)

    虽然,绑定一个类到容器不一定非要通过 服务提供者(ServiceProvider) 。

    但是,我们知道,有时候我们的类、模块会有需要其他类和组件的情况,为了保证初始化阶段不会出现所需要的模块和组件没有注册的情况,laravel 将注册和初始化行为进行拆分,注册的时候就只能注册,初始化的时候就是初始化。拆分后的产物就是现在的 服务提供者

    服务提供者主要分为两个部分,register(注册) 和 boot(引导、初始化),具体参考文档。register 负责进行向容器注册“脚本”,但要注意注册部分不要有对未知事物的依赖,如果有,就要移步至 boot 部分。

    Facade#

    我们现在解答之前关于 Route 的方法为何能以静态方法访问的问题。实际上这个问题文档上有写,简单说来就是模拟一个类,提供一个静态魔术方法__callStatic,并将该静态方法映射到真正的方法上。

    我们使用的 Route 类实际上是 Illuminate\Support\Facades\Route 通过 class_alias() 函数创造的 别名 而已,这个类被定义在文件 vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php 。

    我们打开文件一看……诶?怎么只有这么简单的一段代码呢?

    <?php namespace Illuminate\Support\Facades;
    
    /**
     * @see \Illuminate\Routing\Router
     */
    class Route extends Facade {
    
        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor()
        {
            return 'router';
        }
    
    }

    其实仔细看,会发现这个类继承了一个叫做 Facade 的类,到这里谜底差不多要解开了。

    上述简单的定义中,我们看到了 getFacadeAccessor 方法返回了一个 route,这是什么意思呢?事实上,这个值被一个 ServiceProvider 注册过,大家应该知道注册了个什么,当然是那个真正的路由类!

    有人会问,Facade 是怎么实现的。我并不想说得太细,一个是我懒,另一个原因就是,自己发现一些东西更容易理解,并不容易忘记。很多细节我已经说了,建议大家自行去研究。

    至此,我们已经讲的差不多了。

    和平!我们该总结总结了!#

    无论如何,世界和平了。

    这里要总结的内容就是,其实很多事情并不复杂,怕的是复杂的理论内容。我觉得很多东西一旦想通也就那么回事儿。很多人觉得 laravel 这不好那不好、这里难哪里难,我只能说,laravel 的确不是一流和优秀的框架,说 laravel 是一流、优秀的框架的人,不是 laravel 的粉丝那么就是跟风炒作。Laravel 最大的特点和优秀之处就是使用了很多 php 比较新(实际上并不新)的概念和技术(也就一堆语法糖)而已。因此 laravel 的确符合一个适宜学习的框架。Laravel 的构思的确和其他框架有很大不同,这也要求学习他的人必须熟练 php,并 基础扎实!如果你觉得学 laravel 框架十分困难,那么原因只有一个:你 php 基础不好。

    另外,善于利用命名空间和面向对象的诸多特性,去追寻一些东西,你会发现,原来这一切这么容易。



    展开全文
  • 当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。...

    原文:laravel 学习笔记 —— 神奇的服务容器

    容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。

    IoC 容器, laravel 的核心

    Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。

    本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。

     

    IoC 容器诞生的故事

    讲解 IoC 容器有很多的文章,我之前也写过。但现在我打算利用当下的灵感重新来过,那么开始吧。

    超人和超能力,依赖的产生!

    面向对象编程,有以下几样东西无时不刻的接触:接口还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。

    怪物横行的世界,总归需要点超级人物来摆平。

    我们把一个“超人”作为一个类,

    class Superman {}

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

    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) {}
    }

    *为了省事儿我没有详细写出 __construct() 这个构造函数的全部,只写了需要传递的参数。

    好了,这下我们的超人有点“忙”了。在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:

    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)
            );
            */
        }
    }

    我们需要自己手动的在构造函数内(或者其他方法里)实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力,或者需要 变更 超能力的方法,我们必须 重新改造 超人。换句话说就是,改变超能力的同时,我还得重新制造个超人。效率太低了!新超人还没创造完成世界早已被毁灭。

    这时,灵机一动的人想到:为什么不可以这样呢?超人的能力可以被随时更换,只需要添加或者更新一个芯片或者其他装置啥的(想到钢铁侠没)。这样的话就不要整个重新来过了。

    对,就是这样的。

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

    工厂模式,依赖转移!

    当然,实现控制反转的方法有几种。在这之前,不如我们先了解一些好玩的东西。

    我们可以想到,组件、工具(或者超人的模组),是一种可被生产的玩意儿,生产的地方当然是 “工厂(Factory)”,于是有人就提出了这样一种模式: 工厂模式

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

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

    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 即可。扩充超能力的同时不再需要重新编辑超人的类文件,使得我们变得很轻松。但是,这才刚刚开始。

    再进一步!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]);
                // 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);
    }

    上文中,我们定下了一个接口 (超能力模组的规范、契约),所有被创造的模组必须遵守该规范,才能被生产。

    其实,这就是 php 中 接口( interface ) 的用处和意义!很多人觉得,为什么 php 需要接口这种东西?难道不是 java 、 C# 之类的语言才有的吗?这么说,只要是一个正常的面向对象编程语言(虽然 php 可以面向过程),都应该具备这一特性。因为一个 对象(object) 本身是由他的模板或者原型 —— 类 (class) ,经过实例化后产生的一个具体事物,而有时候,实现统一种方法且不同功能(或特性)的时候,会存在很多的类(class),这时候就需要有一个契约,让大家编写出可以被随时替换却不会产生影响的接口。这种由编程语言本身提出的硬性规范,会增加更多优秀的特性。

    虽然有些绕,但通过我们接下来的实例,大家会慢慢领会接口带来的好处。

    这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类:

    /**
     * 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 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。

    不过我告诉大家,这种自动搜寻依赖需求的功能,是通过 反射(Reflection) 实现的,恰好的,php 完美的支持反射机制!关于反射,php 官方文档有详细的资料,并且中文翻译基本覆盖,足够学习和研究!

    http://php.net/manual/zh/book.reflection.php

    现在,到目前为止,我们已经不再惧怕怪兽们了。高智商人才集思广益,井井有条,根据接口契约创造规范的超能力模组。超人开始批量产出。最终,人人都是超人,你也可以是哦 stuck_out_tongue_closed_eyes

    回归正常世界。我们开始重新审视 laravel 的核心。

    现在,我们开始慢慢解读 laravel 的核心。其实,laravel 的核心就是一个 IoC 容器,也恰好是我之前所说的高级的 IoC 容器。

    可以说,laravel 的核心本身十分轻量,并没有什么很神奇很实质性的应用功能。很多人用到的各种功能模块比如 Route(路由)Eloquent ORM(数据库 ORM 组件)Request and Response(请求和响应)等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被你所使用,其实都是 laravel 的服务容器负责的。

    我们以大家最常见的 Route 类作为例子。大家可能经常见到路由定义是这样的:

    Route::get('/', function() {
        // bla bla bla...
    });

    实际上, Route 类被定义在这个命名空间:Illuminate\Routing\Router,文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php

    我们通过打开发现,这个类的这一系列方法,如 getpostany 等都不是静态(static)方法,这是怎么一回事儿?不要急,我们继续。

    服务提供者

    我们在前文介绍 IoC 容器的部分中,提到了,一个类需要绑定、注册至容器中,才能被“制造”。

    对,一个类要被容器所能够提取,必须要先注册至这个容器。既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器,那么提供服务并绑定服务至容器的东西,就是 服务提供者(ServiceProvider)

    虽然,绑定一个类到容器不一定非要通过 服务提供者(ServiceProvider) 。

    但是,我们知道,有时候我们的类、模块会有需要其他类和组件的情况,为了保证初始化阶段不会出现所需要的模块和组件没有注册的情况,laravel 将注册和初始化行为进行拆分,注册的时候就只能注册,初始化的时候就是初始化。拆分后的产物就是现在的 服务提供者

    服务提供者主要分为两个部分,register(注册) 和 boot(引导、初始化),具体参考文档。register 负责进行向容器注册“脚本”,但要注意注册部分不要有对未知事物的依赖,如果有,就要移步至 boot 部分。

    Facade

    我们现在解答之前关于 Route 的方法为何能以静态方法访问的问题。实际上这个问题文档上有写,简单说来就是模拟一个类,提供一个静态魔术方法__callStatic,并将该静态方法映射到真正的方法上。

    我们使用的 Route 类实际上是 Illuminate\Support\Facades\Route 通过 class_alias() 函数创造的 别名 而已,这个类被定义在文件 vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php 。

    我们打开文件一看……诶?怎么只有这么简单的一段代码呢?

    <?php namespace Illuminate\Support\Facades;
    
    /**
     * @see \Illuminate\Routing\Router
     */
    class Route extends Facade {
    
        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor()
        {
            return 'router';
        }
    
    }

    其实仔细看,会发现这个类继承了一个叫做 Facade 的类,到这里谜底差不多要解开了。

    上述简单的定义中,我们看到了 getFacadeAccessor 方法返回了一个 route,这是什么意思呢?事实上,这个值被一个 ServiceProvider 注册过,大家应该知道注册了个什么,当然是那个真正的路由类!

    有人会问,Facade 是怎么实现的。我并不想说得太细,一个是我懒,另一个原因就是,自己发现一些东西更容易理解,并不容易忘记。很多细节我已经说了,建议大家自行去研究。

    至此,我们已经讲的差不多了。

    和平!我们该总结总结了!

    无论如何,世界和平了。

    这里要总结的内容就是,其实很多事情并不复杂,怕的是复杂的理论内容。我觉得很多东西一旦想通也就那么回事儿。很多人觉得 laravel 这不好那不好、这里难哪里难,我只能说,laravel 的确不是一流和优秀的框架,说 laravel 是一流、优秀的框架的人,不是 laravel 的粉丝那么就是跟风炒作。Laravel 最大的特点和优秀之处就是使用了很多 php 比较新(实际上并不新)的概念和技术(也就一堆语法糖)而已。因此 laravel 的确符合一个适宜学习的框架。Laravel 的构思的确和其他框架有很大不同,这也要求学习他的人必须熟练 php,并 基础扎实!如果你觉得学 laravel 框架十分困难,那么原因只有一个:你 php 基础不好。

    另外,善于利用命名空间和面向对象的诸多特性,去追寻一些东西,你会发现,原来这一切这么容易。

    展开全文
  • 因为包含了一大堆所谓“先进”的概念,其中依赖注入(DI)Ioc容器是比较核心的内容之一。 我百度了一下,讲PHP DIIoc的内容很少,更别说详解Laravel ioc的了。 在这里,我综合了几篇写得比较典型的文章,以一...

    Laravel这个框架,用起来方便,理解起来不简单。

    为什么不简单?因为包含了一大堆所谓“先进”的概念,其中依赖注入(DI)和Ioc容器是比较核心的内容之一。

    我百度了一下,讲PHP DI和Ioc的内容很少,更别说详解Laravel ioc的了。

    在这里,我综合了几篇写得比较典型的文章,以一个产品经理的身份,从用户体验的角度尝试让初学者也能比较容易理解这个2个概念。

     

    DI和Ioc就是一种设计模式,这个概念已经存在10几年了,请先对面向对象编程和设计模式(DesignPatterns)有初步的理解:

    预先了解一下这三种模式:1. 工厂模式 2. 单例模式 3. 注册器模式

    也请了解一下 interface 的用法和作用;

     

    如果上述内容你都不是很熟悉,没关系,很简单的,请接着看:

     

    1. 首先来看 依赖注入(DI):

    故事是这样的:

    我们知道写一个类的时候,类本身是有个目的的,类里面有很多方法,每个方法搞定一些事情;

     

    class Hookup {

     

    public function getPhone(){

         //此处省略

    }

     

    public function inviteHer(){

         //此处省略

    }

     

    public function giveGifts(){

         //此处省略

    }

    public function kissHer(){

         //此处省略

    }

    }

    比方说

    这个类是什么意思?Hookup就是泡妞的意思,里面有4个步骤,第一步打电话,第二部邀请她共进晚餐,第三步送礼物,第四步打波儿,第五步.....我只能帮你到这里了...

     

    别看只有4步,其实每一步都不简单,第一步打电话,你得先要到人家电话吧,第二步邀请吃饭,你得提前订好饭店吧,第三步送礼物,你得先买礼物吧~第四步Kiss,你总得抓住一个合适的机会吧;

     

    当你在专心处理Hookup的时候,我太了解你了,你只关心结果,也就是抱得美人归,问电话号码,订餐,买礼物,找机会这种小事,就交给其他“类”处理吧。

     

    如下:

     require 'BuyGifts.php';

     

     

    class Hookup {

     

    public function getPhone(){

         //此处省略

    }

     

    public function inviteHer(){

         //此处省略

    }

     

    public function giveGifts(){

    $gift = new BuyGifts();

    $gift->select();

    $gift->pay();

    }

    public function kissHer(){

         //此处省略

    }

    }

     

    $paoniu = new hookup();

    $paoniu->giveGifts();

     

    这里面送礼物这个环节(比较懒,其他的不写了),就引入了BuyGifts这个类,买礼物这种小事,就交给BuyGifts这个秘书去处理吧。

     

    所以,一切问题的出发点,就是在一个类中调用其他类,我们要解决的,就是在这过程中会发生的问题。

     

    Hookup这个可以称之为主类,BuyGifts称之为次类,为了实现主类的目标,要依赖很多次类来配合。

     

    好,现在你已经知道什么是依赖了。

     

    问题来了。现在讲正经的。

     

    比方说你很忙,你不仅要泡妞,还要开公司,做生意,没事打打高尔夫球什么的。这每一件事都是一个“主类”,BuyGifts 这种“次类” ,除了泡妞,也可以放在 做生意这个类里面调用,做生意也要送礼的嘛,当然还可以应用在别的主类。 

     

    如果BuyGifts被调用很多次,哪一天需要把名字改了,那你就必须在众多主类中逐一修改。

    还有就是,每一次调用都要new实例化一次。

    这样做不科学,你也知道。

     

    怎么办: 工厂模式

    工厂模式很简单,就是来帮助你批量实例化“次类”的。也就是说,用了工厂模式,你的主类中将不会再出现new这样的关键字。

     

    看实例,有3个类 哦:

     

    -----   Factory.php    ----------

     

    include 'BuyGifts.php'

     

    class Factory {

     

    static function getPhone(){

     

    return new getPhone;

    }

     

     

    static function BuyGifts(){

     

    return new BuyGifts;

    }

     

    //……下面还有很多,此处省略

     

    }

     

    -----   BuyGifts.php    ----------

     

    class  BuyGifts{

     

    public function select(){

    //此处省略 }

     

    public function pay(){

    //此处省略 }

     

    }

     

    -----   Hookup.php    ----------

     

    require 'BuyGifts.php';

    require 'Factory.php ';

     

    class Hookup {

     

    private $gift;

     

    public function getPhone(){

         //此处省略

    }

     

    public function inviteHer(){

         //此处省略

    }

     

    public function giveGifts(){

    $this->gift = Factory::BuyGifts();

    $gift->select();

    $gift->pay();

    }

    public function kissHer(){

         //此处省略

    }

    }

     

    $paoniu = new hookup();

    $paoniu->giveGifts();

     

     

    你看,现在主类Hookup 要调用次类BuyGifts,就得先去找Factory类,Factory就变成中介了。

    Factory这个中介的主要服务就是帮 你实例化次类,另外管理很多次类(工厂嘛),这样你就不用直接与次类发生关系。

     

    这个过程就叫做 解耦,不管次类怎么变,你找工厂就可以了。

     

    可是这样做问题依旧存在,,当我们在很多主类里调用了工厂及其方法,可是有一天发现工厂类要改名,,或者工厂里面的方法要改名呢?那我们还不是得逐一改主类?虽然这种情况不多,但是这种不讲信誉的“黑中介”也是偶尔会出现的。

     

    怎么办呢?我们对这个Factory 中介 也得 防 一手。

     

    -----   Hookup.php    ----------

     

    require 'BuyGifts.php';

    require 'Factory.php ';

     

    class Hookup {

     

    private $gift;

     

    public function getPhone(){

         //此处省略

    }

     

    public function inviteHer(){

         //此处省略

    }

     

    public function giveGifts(){

    $gift->select();

    $gift->pay();

    }

     

    public function kissHer(){

         //此处省略

    }

     

    public function setGift($instance){

    $this->gift = $instance;

    }

    }

     

    $paoniu = new hookup();

    $paoniu->setGift(Factory::BuyGifts()); // 看到 Factory已经滚粗 我们的主类了

    $paoniu->giveGifts();

     

    现在Hookup类就像一个公司,作为老板的你只关心怎么泡妞,脏活累活交给别人干,于是你设立了一个setGift采购部门,这个部门专门负责和Factory中介打交道,这样Factory中介就完全滚粗你的视野了。

     

    Factory这个被依赖的对象 是通过参数 从外部 注入到 类内容的 静态 属性 实现实例化的,这个就是依赖注入。

     

    可以聪明的你马上发现,我擦,setGift 你这个部门就负责采购gift啊?我要打电话,发预约邀请难道还要setPhone,setInvitation吗?

     

    这样主类里面要写很多set方法,外面要调用很多次set方法,你会发现,更不科学了。

    里面:

    public function setGift($instance){

    $this->gift = $instance;

    }

    public function setPhone($instance){

    $this->phone = $instance;

    }

     

    public function setInvitation($instance){

    $this->Invitation = $instance;

    }

     

    ….

     

    外面:

     

    $paoniu->setGift(Factory::BuyGifts());

    $paoniu->setPhone(Factory::GetPhone());

    $paoniu->setInvitation(Factory::SendInvitation());

     

    ….

     

     

    这个时候,你已经把Factory 赶出 Hookup公司了,所以,公司内部的事情,只能你自己搞定了。

    公司外部的业务,这时Factory中介 又屁颠屁颠的跑过来找你,提出一个方案:

     

    我们来搞个“总代”怎么样?

     

    class TopFactory {

     

    public static function all_Agents_in_one(){

     

    $paoniu_agent = new hookup();

              $paoniu_agent->setGift(Factory::BuyGifts()); 

    $paoniu_agent->setPhone(Factory::GetPhone());

             $paoniu_agent->setInvitation(Factory::SendInvitation());

     

    return $paoniu_agent;

    }

     

    }

     

    黑中介Factory对你说:“你看我搞了个总代公司(TopFactory),你在外面也不要new啊,set什么的了,我全包了。

     

    于是现在你在外面要做的变简单了:

     

    $paoniu = TopFactory::all_Agents_in_one();

    $paoniu->giveGifts();//giveGifts里面就可以调用Buygifts了;

    $paoniu->getPhone();

    ….

     

     

    到现在为止,你已经有一套完整的 依赖注入方案了,这个方案组建的时候虽然有点复杂(层层转包),但是一旦建好,维护起来比较方便。

     

    可是,别忘了公司内部的事情还没解决哪,另外要提醒你,你是程序员,不是老板,所以Factory那一部分也是你的活~~~

     

     

    需要一种更高级的方案,让程序员的生活变得Easier一些。

    这个方案就是IoC容器,IoC容器首先是一种类注册器,其次它是一种更高级的依赖注入方式。

     

    它和工厂Factory其实性质一样,都是中介代理,但实现机制不一样。

     

     

    工厂Factory 把 次类 一一对应 注册到 类中的 实例化静态方法中;

    IoC容器是把 次类 实例化对象 依次 注册到 类中一个静态数组;

     

     

    IoC容器的设计模式叫做 注册器模式;

     

    看实例:

    ———Di.php——-

    class Di {

     

    protected static $objects;

     

    static function set($key, $object){

     

    self::$objects[$key]=$object;

     

    }

     

    static function get ($key){

     

    return self::$objects[$key];

     

    }

     

    static function unset ($key){

     

    unset(self::$objects[$key]);

     

    }

     

     

    }

     

     

    -----   Hookup.php    ----------

     

    require ‘BuyGifts.php';

    require 'Factory.php ';

    require 'Di.php ';

     

    class Hookup {

     

    private $di;

     

    function __construct(Di &$di){

           $this->_di = $di;

        }

     

    public function getPhone(){

         //此处省略

    }

     

    public function inviteHer(){

         //此处省略

    }

     

    public function giveGifts(){

    $this->di->get(‘BuyGifts’)->select();

    $this->di->get(‘BuyGifts’)->pay();

    }

     

    public function kissHer(){

         //此处省略

    }

     

    public function setGift($instance){

    $this->gift = $instance;

    }

    }

     

    $di = new Di();

    $di->set(‘BuyGift’, function(){

    return Factory::BuyGift();

    }

     

     

    $paoniu=new Hookup($di);

    $paoniu->giveGifts();

     

     

    ———-

     

    可以看到,IoC模式才是真正的总代,它连公司内部的事情也管了。和设立set部门相比,set只能处理一个个单独的业务,比如setGift,setPhone,setInvitation,之类的,,而ioc容器把这些实例全部放在一个数组里统一处理,并且还可以重新命名(键名),实现了完全的解耦。

     

     

     

    请注意的是,我们在注册Ioc容器的时候,是这样写的:

    $di->set(‘BuyGift’, function(){

    return Factory::BuyGift();

    }

     

    而没有这样写:

    $di->set(‘BuyGift’, Factory::BuyGift());

     

     

    第一个参数是数组的键名,第二个参数是数组的值;

     

    第一种写法用了回调函数,它只有在读取数组的值的时候才会被执行;

    第二种方法直接实例化成一个对象,保存到数组的值中。第二种方法会比较占资源。

    另外我们发现

    $di = new Di();

    $di->set(‘BuyGift’, function(){

    return Factory::BuyGift();

    }

     

     

    $paoniu=new Hookup($di);

    $paoniu->giveGifts();

     

     

    这里面怎么还有new呢,我们用静态方法和Factory写得优雅一些:

    Di::set(‘BuyGift’, function(){

    return Factory::BuyGift();

    } //注册到容器

    $paoniu=factory::Hookup(factory::DI()); // 将容器注入主类

    $paoniu->giveGifts(); //这里就可以调用BuyGift()这个次类(依赖类);

     

     

     

    好了,我们来看看Laravel Ioc容器 是怎么用的:

     

    绑定类到容器

     

    App::bind('foo', function()

    {

        return newFooBar;

    });

     

    App 就是Ioc容器,bind就是set方法,’foo’ 就是数组中 实例的 键名;

     

     

    至此,我们已经对Ioc容器有了一个比较清楚的认识,我们已经了解了工厂模式,注册模式,依赖注入等知识,其实依赖注入有3种方式,,一种就是set注入(单类注入),一种是容器注入(Ioc),还有一种是接口注入(Interface);前两种我们已经学习过了,后一种放在本文的第二部分来讲。

     

    在本文的第二部分,我们将详细讲解LaravelIoC的用法,为什么说Laravel理解起来比较难,因为它的设计不是入门级别的,而是专业级别的,比前面讲的要复杂许多,但是看官方文档,寥寥数语,真有种想砸电脑的冲动。

    展开全文
  • Laravel中 使用composer下载包时 会在vendor下有个autoload.php文件  这个autoload.php文件解决的是自动加载的问题,通俗点讲:就是通过 _autoload 或者 spl_autoload_register 来解决频繁使用 require_once
    参考实例

    1. Laravel中 使用composer下载包时  会在vendor下有个autoload.php文件 
    这个autoload.php文件解决的是自动加载的问题,通俗点讲:就是通过 _autoload 或者  spl_autoload_register 来解决频繁使用 require_once  的问题 
    _autoload是在new Class实例化的时候自动调用的函数 

    1. 依赖注入:通俗点讲:就是在一个class a中 需要new 另外一个class B 那么在实例化a的时候就依赖B   它解决的是何时去new Class 实例化的问题  
    class a{
        Private $b;
        Public  function __construct(){
             $b = new B(); 
         }
    }


    工厂模式 : 通俗点说,就是在一个类中 将所有的 new Class 实例化的工作去交给一个工厂类去办,工厂类直接返回实例
    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])
            );
            */
        }
    }

    IoC模式:通俗点说,就是不去new Class啦,在一个类中如果需要另外一个class,那么就直接将这个class的实例作为参数传递进去
    class Superman
    {
        protected $module;
        public function __construct(SuperModuleInterface $module)
        {
            $this->module = $module;
        }
    }

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

    public function store(Request $request)   //这里$request 就是Request类的实例 作为参数传递进来  直接使用$request来获取get或者post过来的数据
    {
        $name => $request->name,
        $email=> $request->email,

    }


    IoC容器:通俗点说,就是升级版的“超级工厂” 也就是将IoC和工厂模式结合起来,通过向“超级工厂” 添加闭包函数来将IoC模式中的 new XPower new Superman通过工厂模式生产出来

    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->make($moduleName)就是IoC模式中 向Superman类注入的实例
    });

    // 向该 超级工厂添加超能力模组的生产脚本
    $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');
    // ...随意添加

    laravel中的 服务容器 其实就是一个IoC容器   服务器提供者其实就是将bind,make这些同一放到一个serviceProvider类中
    关于服务容器,服务提供者的进一步理解请参考 <<laravel关键技术解析第150页>>
    展开全文
  • 在本篇文章中,我们将通过 Laravel 的 IoC 组件探索依赖注入设计模式,并了解它如何改进我们的设计。 依赖注入 依赖注入一词是由 Martin Fowler 提出的术语,它是将组件注入到应用程序中的一种行为。就像 Ward ...
  • Java的依赖注入已经是一个很常见的概念了,Spring框架主要就是解决了这一点,在PHP的laravel框架中,也出现了依赖注入的方式。 依赖注入就控制反转的一种是实现方式,面向对象的特征的重要体现,那么依赖注入中什么...
  • Laravel:依赖注入

    千次阅读 2019-05-29 17:26:18
    不过,也不要被这个名头吓住,要知道 IoC 容器只不过是一种用于方便我们实现「依赖注入」这种软件设计模式的工具。而且要实现依赖注入并不一定非要通过 IoC 容器,只是使用 IoC 容器会更容易一点儿。 首先,来看看...
  • 自动依赖注入什么是依赖注入,用大白话将通过类型提示的方式向函数传递参数。实例 1首先,定义一个类:/routes/web.php class Bar {}假如我们在其他地方要使用到 Bar 提供的功能(服务),怎么办,直接传入参数即可...
  • 深入理解Laravel容器概念,DI依赖注入,IOC控制反转 IOC - 控制反转 DI - 依赖注入 这两个存在的目的都是为了解耦! 解耦可以理解为,原本紧密结合的两个磁铁,现在我们在他们中间加一层木板,强行将他们分开,却不...
  • GitHub源码地址 摘取的Laravel框架中,Container的实现代码,并参考Laravel中关于容器的使用手册,写的一个容器使用示例。 详情请移步:GitHub
  • 如果说laravel框架的核心是什么,那么无疑是服务容器。理解服务容器的概念,对于我们使用laravel太重要了,应该说是否理解服务容器的概念是区分是否入门laravel的重要条件。因为整个框架正是在服务容器这一基础上...
  • laravel依赖注入

    千次阅读 2017-12-13 09:55:00
    laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。  具体分析参照:http://laravelacademy.org/post/769.html 通常我们的调用如下。 $config = $container...
  • Laravel拥有强大的控制反转(IoC)/依赖注入(DI) 容器。不幸的是官方文档并没有涵盖所有可用的功能,因此,我决定尝试写文档为自己记录一下。以下是基于Laravel 5.4.26,其他版本可能有所不同。...
  • 在使用laravel的时候,可以看到大量地使用了依赖注入。比如控制器中的HttpRequest对象,各种Model的实现类等等。这种实现方式的好处在于不需要再方法中频繁地new某些实例,实现模块的解耦。 依赖注入使用PHP反射API...
  • Laravel控制反转+ 依赖注入

    千次阅读 2019-06-06 16:10:53
    1. 控制反转 ... 1.1. Ioc(容器)是什么 Ioc - Inversion of Control 即 ...Ioc意味着将你设计好额对象放再一个叫容器控制里面(也就是你所封装好的对象方法常用的放在一个容器里面,容器里面可以做好关联,以便后面...
  • laravel依赖注入原理

    千次阅读 2017-06-07 11:17:04
    laravel依赖注入浅析laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。 具体分析参照:http://laravelacademy.org/post/769.html通常我们的调用如下。$config =...
  • 所以,Laravel为了方便管理这些服务(实现解耦)决定,不再直接调用这些服务,例如开发者定义了一个方法1,原本服务B绑定,通过服务B实现,现在不要B了,而是定义了一个接口A,通过接口A去实现这个服务B。...
  • 最近在看设计模式中的依赖注入,希望借助设计模式的神奇魔力,能达到一个目的,然后在此学习的过程中,能收获一个bonus。这个目的就是能使得自己设计的系统更简单更容易理解,或者是使得系统设计的结构代码更简单...
  • laravel和依赖注入

    2019-01-07 22:07:41
    laravel中的依赖注入: 简单绑定:每次注入的时候都需要实例化 单例绑定:只在容器中实例化一次 绑定实例:绑定一个已经实例化的对象 绑定原始值:规定在某个控制器中注入时,自动传入参数 make方...
  • laravel iocAs developers, we are always trying to find new ways to write well designed and clean code by adopting new styles, using design patterns, and trying new robust frameworks. In this article w...
  • 要想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题:  DI——Dependency Injection 依赖注入 IoC——Inversion of Control 控制反转 1、参与者都有谁?  答:一般有三方参与者,一个是某个对象;...
  • 1、控制反转(IoC): 不需要自己内容修改,改成由外部传递。从依赖内部改为依赖外部。 2、依赖注入(DI):不是由自己内部new对象或者实例,而是通过构造函数或方法传入... 控制反转和依赖注入 左边的问题:...
  • 理解laravel依赖注入与控制反转

    千次阅读 2017-10-17 17:51:32
    依赖注入 当我第一次接触这个词的时候,我是有些丈二和尚摸不着头脑的,至今我也是感到比较困惑的,所以今天我们来探索一下Laravel中的依赖注入(dependency injection), 来好好的理解它。 控制反转 第一印象是好深奥...
  • 依赖注入,简单说是把类里头依赖的对象,置于类外头,即客户端调用处。相当于把类与类解耦。  一个简单的例子:   class A { public function __construct() { // 这种实例化写法, A类的内部,依赖了B类...
  • 什么是依赖注入和控制反转? 控制反转:即IOC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的...
  • Laravel依赖注入浅析

    千次阅读 2018-10-12 10:14:47
    laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。 具体分析参照:http://laravelacademy.org/post/769.html 通常我们的调用如下。 $config = $container-&...
  • 记录一下这篇 重写仿照 laravel 的 IOC 容器和依赖注入 的代码 <?php class Boos{ //领导依赖员工 private $staff; //老板只需要告诉外部我需要什么样的人就好了,其它什么都不管,具体什么样的人交给外部...
  • //到这里,看来是无法通过容器自动注入PaySettingsInterface的实现了。那么我就只能这样了。在测试代码中: public function testPay(){ $orderSn = Strings::randomString('NUMBER+',12); $order = factory...
  • 什么是依赖注入? 耦合性很强的例子: class A { public function __construct() { // A类的内部,依赖了B类 $this->b = new B(); } public function printStrA(){ $this->b->printStrB(); .....
  • 当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,811
精华内容 724
关键字:

laravel容器和依赖注入