laravel 核心架构(1)服务容器-深入理解控制反转(IoC)和依赖注入(DI)

1. 介绍

laravel 容器

存放的 是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦”、“依赖注入(DI)”。

服务容器的使用

2. 通过案例解析IoC和DI

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

工厂模式,依赖转移

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

class Superman4
{
     
    protected $power;

    public function __construct(array $modules)
    {
     
        // 1 单个能力,依赖
//        $this->power = new Power(999, 100);


        // 2 多个能力,依赖
        $this->power = new Flight(9, 100);
//        $this->power = new Force(45);
//        $this->power = new Shot(99, 50, 2);
        $this->power = [
            new Force(45),
            new Shot(99, 50, 2),
        ];

        // 3 多个能力,工厂模式(依赖)
        $factory = new SuperModuleFactory();

        // 和之前区别不大,但没有那么多new关键字
        $this->power[] = $factory->makeModule('Flight', [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])
        );

        // 4 工厂模式更改(依赖)
        // 由原来对多个外部的依赖变成了对一个 “工厂” 的依赖
        $factory = new SuperModuleFactory();

        foreach ($modules as $moduleName => $moduleOptions){
     
            $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
        }


    }
}

如果需要增加能力,还需要更改工厂类 ,噩梦般的感受!。。。

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

实现统一种方法且不同功能(或特性)的时候,会存在很多的类(class),这时候就需要有一个契约,即接口
可以被随时替换却不会产生影响的接口

只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),
而是由外部以参数或其他形式注入的,都属于依赖注入(DI)

依赖注入案例(手动创建并注入):

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

低效率产出(手动)怎么行? 需要自动化!
这种更为高级的工厂,就是工厂模式的升华 —— IoC 容器

class main
{
     
    /**
     * main constructor.
     * 创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个“生产脚本”即可。
     */
    public function __construct()
    {
     
        // 创建一个容器
        $container = new Container;

        /**
         * 通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)
         * 作为生产一个类的实例的 脚本 ,
         * 只有在真正的 生产(make) 操作被调用执行时,才会触发。
         */

        // 向 容器 添加 超人的生产脚本
        // 缺点,手动提供超人的模组参数 (更高级的Ioc容器可以自动化处理 - 反射实现)
        $container->bind('superman', function ($container, $moduleName){
     
            // moduleName 参数
            return new Superman($container->make($moduleName));
        });

        // 向 容器 添加 x射线能力的生产脚本
        $container->bind('xpower', function ($container){
     
            return new XPower;
        });

        // 向 容器 添加 炸弹能力的生产脚本
        $container->bind('ultrabomb', function ($container){
     
            return new UltraBomb;
        });

        // 开始启动生产
        $superman1 = $container->make('superman', 'xpower');
        $superman2 = $container->make('superman', 'ultrabomb');
        $superman3 = $container->make('superman', 'xpower');
        // ...随意添加
    }

}

自动注入参数 - 反射

手动提供超人所需要的模组参数,但真正的 IoC
容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
Laravel框架的服务容器正是这么做的。
这种自动搜寻依赖需求的功能,是通过反射(Reflection)实现的

3. Laravel 核心 - IoC 容器

功能模块
比如 Route(路由)、Eloquent ORM(数据库 ORM 组件)、Request(请求)以及 Response(响应)等,
都是与核心无关的类模块提供
这些类从注册到实例化,都是 Laravel 的服务容器负责的

服务提供者

一个类要被容器所能够提取,必须要先注册至这个容器
一个类需要绑定、注册至容器中,才能被“制造”
例如
需要某个服务,就得先注册、绑定这个服务到容器,
那么提供服务并绑定服务至容器的东西,就是服务提供者(Service Provider)。

服务提供者
主要分为两个部分,register(注册) 和 boot(引导、初始化)

class AppServiceProvider extends ServiceProvider
{
     
    /**
     * Bootstrap any application services.
     * 引导、初始化
     *
     * @return void
     */
    public function boot()
    {
     
        //
    }

    /**
     * Register any application services.
     * 向容器注册"脚本"
     * 注册部分如果存在对未知事物的依赖,写在boot中
     *
     * @return void
     */
    public function register()
    {
     
        //
    }
}

route静态访问

route类 get,post,any 等都不是静态(static)方法,
却能以静态方法访问

# vendor/laravel/framework/src/Illuminate/Routing/Router.php
/**
 * Register a new GET route with the router.
 *
 * @param  string  $uri
 * @param  \Closure|array|string|null  $action
 * @return \Illuminate\Routing\Route
 */
public function get($uri, $action = null)
{
     
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
Route::get('/', function() {
     
    // bla bla bla...
});

使用了 门面(Facade)

模拟一个类,提供一个静态魔术方法__callStatic,
并将该静态方法映射到真正的方法上。

Route 类实际上是 Illuminate\Support\Facades\Route 通过 class_alias()
函数创造的别名而已,这个类被定义在文件
vendor/laravel/framework/src/Illuminate/Support/Facades/Route.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 注册了真正的路由类 ????

你可能感兴趣的:(laravel 核心架构(1)服务容器-深入理解控制反转(IoC)和依赖注入(DI))