zoukankan      html  css  js  c++  java
  • 深入 Laravel 内核之IOC容器

    升级工厂前的准备工作

    无规矩不成方圆,随着越来越多的行为出现,我们需要需要定下一些规范。

    为了约束每一个行为的规范,需要定义一个行为接口:

    interface BehaviorInterface
    {
        /**
         * 行为激活方法
         * @param array $values 激活的参数
         */
        public function activate(array $values);
    }
    

    按照接口规范修改前述的行为类:

    class Attack implements BehaviorInterface{
        protected $value = 0;
    
        public function __construct($value)
        {
            $this->value = $value;
        }
        
        public function activate(array $values){}
    }
    
    class Defend implements BehaviorInterface{
        protected $value = 0;
        public function __construct($value){}
        public function activate(array $values){}
    }
    
    class Move implements BehaviorInterface{
        protected $speed;
        public function __construct($speed){}
        public function activate(array $values){}
    }
    
    class Skill1 implements BehaviorInterface{
        protected $name = '暴击';
        public function __construct(){}
        public function activate(array $values){}
    }
    
    class Skill2 implements BehaviorInterface{
        protected $name = '眩晕';
        public function __construct(){}
        public function activate(array $values){}
    }
    

    使用依赖注入升级工厂

    1. 为了解决工厂对行为的依赖,在每一种行为定义好之后就放到工厂中(依赖注入)。
    2. 放到工厂中的行为必须是依照规范定义的。
    
    class BehaviorFactory
    {
        protected $instances;
        
        public function setBehavior($behaviorName, $behavior)
        {
            if($behavior instanceof BehaviorInterface) {
                $this->instances[$behaviorName] = $behavior;
            }
        }
    
        public function getBehavior($behaviorName)
        {
            return $this->instances[$behaviorName];
        }
    }
    
    $factory = new BehaviorFactory();
    $factory->setBehavior('Attack', new Attack(0));
    $factory->setBehavior('Defend', new Defend(0));
    $factory->setBehavior('Move',   new Move(0));
    $factory->setBehavior('Skill1', new Skill1());
    $factory->setBehavior('Skill2', new Skill2());
    

    对英雄类做一些简单的修改:

    class Hero
    {
        protected $behavior = [];
    
        public function __construct($factory, array $behaviors)
        {
            // 通过工厂提供的方法制造需要的模块
            foreach ($behaviors as $behaviorName => $behaviorOptions) {
                $behavior = $factory->getBehavior($behaviorName);
                $this->behavior[] = $behavior->activate($behaviorOptions);
            }
        }
    }
    
    $hero = new Hero($factory, [
    	'Attack' => [10],
    	'Defend' => [5],
    	'Move'   => [30],
    	'Skill1' => [],
    ]);
    

    至此,我们通过依赖注入,已经彻底地解决了英雄对行为的依赖、英雄对工厂的依赖、工厂对行为的依赖。
    但是还有一些不足之处:

    1. 所有的对象,我们都必须手动去实例化;
    2. 这些行为,无论英雄是否需要,都要提前实例化并放到工厂中。

    再谈依赖注入

    前面的文章中我们已经讲解了如何通过PHP的反射机制来解决依赖注入的问题。这里我们再来深入一下对依赖注入的理解。

    什么是依赖注入:
    所谓的依赖注入,意思是只要不是在类内部产生的对象依赖(比如在初始化、构造函数中,通过工厂方法或者手动实例化),而是由外部以参数或其他形式注入(传入)的,都属于依赖注入(DI)。

    超级工厂-IOC服务容器

    接下来将我们的升级工厂,进一步改造为超级工厂。

    定义一个简单的容器类,用来存储对象实例化的方式和创建对象:

    class Container
    {
        // 存放抽象类与对象实例化方式关系的数据
        protected $binds;
    
        // 存放抽象类与已有的对象关系的数据
        protected $instances;
    
        // 将抽象类与[对象实例化方式|已有的对象]分别存入数组
        public function bind($abstract, $concrete)
        {
            // 如果第二个参数是闭包,则存入 $binds 数组
            if ($concrete instanceof Closure) {
                $this->binds[$abstract] = $concrete;
            }
            // 如果第二个参数不是闭包,则为已经实例化的对象,存入 $instances 数组
            else {
                $this->instances[$abstract] = $concrete;
            }
        }
    
        // 根据对象实例化的方式创建对象并返回
        public function make($abstract, $parameters = [])
        {
            // 如果是已经实例化的对象则直接返回
            if (isset($this->instances[$abstract])) {
                return $this->instances[$abstract];
            }
    
            // 将$this(容器)放入参数数组的头部
            // [$this, $param1, $param2...]
            array_unshift($parameters, $this);
    
            // 调用对象实例化的方法(即绑定时传入的闭包函数和当前的参数),得到实例化后的对象并返回
            // function($container, $behaviorName) { return new Hero($behavior); }
            // function($this, 'skill1') { return new Hero('skill1'); }
            return call_user_func_array($this->binds[$abstract], $parameters);
        }
    }
    

    测试这个容器:

    // 创建一个容器
    $container = new Container;
    
    // 向该容器添加英雄的生产方式(实例化的方式)
    $container->bind('hero', function($container, $behaviorName) {
        $behavior = $container->make($behaviorName);
        return new Hero($behavior);
    });
    
    // 向该容器添加行为的生产方式(实例化的方式)
    $container->bind('skill1', function($container) {
        return new Skill1;
    });
    
    // 同上
    $container->bind('skill2', function($container) {
        return new Skill2;
    });
    
    // 开始启动生产
    $hero1 = $container->make('hero', ['skill1']);
    $hero2 = $container->make('hero', ['skill2']);
    

    在这个容器中,我们通过绑定操作,可以向超级工厂注册很多各种各样的生产脚本(可以是匿名函数、非匿名函数、类的方法),这些脚本在生产操作触发的时候就会被容器调用执行。
    通过依赖注入和容器,我们彻底解决了所有对象之间的依赖关系;最重要的是,容器类也没有和英雄、行为之间有任何的依赖。另外也不需要再去手动实例化对象,还实现了按需实例化。

    实际上,真正的 IoC 容器更为高级,会根据类的依赖需求,使用PHP的反射(Reflection)机制,自动在注册、绑定的一堆实例中搜寻符合的相关依赖,并自动注入到构造函数参数中去。

  • 相关阅读:
    五分钟搭建起一个包含CRUD功能的JqGrid表格
    TDD学习笔记【六】一Unit Test
    CQRS
    开源一个vue2的tree组件
    权限管理[Linux]
    文件管理[Linux]
    查看文本[Linux]
    常用命令[Linux]
    文件管理[Linux]
    状态机工作流
  • 原文地址:https://www.cnblogs.com/danhuang/p/13219581.html
Copyright © 2011-2022 走看看