• 详解依赖注入(DI)和Ioc容器


    简单的来说,关键技术就是:注册器模式。  

    场景需求

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

    另外这个主类会依赖一些其他类的帮忙,我们叫这些类为次类,为了实现主类的目标,要依赖很多次类来配合,而且次类很可能被广泛主类依赖,例如:日志类。

    编程思路

    现在我们就举个例子,我需要用到一个泡妞类Hookup主类,里面有“送礼物”这个方法,当然还有其他方法。我们只拿“送礼物”这个方法来举例说明。

    当我们用到泡妞类的时候,我们只关心里面的直接方法“送礼物”,而“送礼物”里面还要“选礼物,付款”等操作,,至于怎么选礼物,怎么付款,那不关我的事情,这些依赖次类BuyGift的配合了。

    require 'BuyGift.php';
    class Hookup{
        //送礼物
        function giveGift(){
            $gift = new BuyGift();
            $gift->select();
            $gift->pay();
      }
    } 
    $paoniu = new Hookup();
    $paoniu->giveGift();

    问题根本

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

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

    问题来了。

    BuyGift 这种“次类” ,除了泡妞,也可以放在 做生意这个类里面调用,做生意也要送礼的嘛,当然还可以应用在别的主类。 

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

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

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

    怎么办? 工厂模式

    class Factory {
        static function BuyGifts(){
            return new BuyGifts;
        }
    }
    
    class Hookup{
        public function giveGift(){
            $this->gift = Factory::BuyGift();
            $gift->select();
            $gift->pay();
        }
    }        
    $paoniu = new Hookup();
    $paoniu->giveGift();

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

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

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

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

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

    依赖注入

    class Factory {
        static function BuyGifts(){
            return new BuyGifts;
        }
    }
    
    class Hookup{
        private $gift;
    
        public function giveGift(){
            $this->gift->select();
            $this->gift->pay();
        }
    
        public function setGift($instance){
            $this->gift = $instance;
        }
    }        
    $paoniu = new Hookup();
    $paoniu->setGift(Factory::BuyGifts()); // 看到Factory已经滚粗我们的主类了
    $paoniu->giveGift();

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

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

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

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

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

    class Factory {
        static function BuyGifts(){
            return new BuyGifts;
        }
    }
    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;
        }
    }
    class Hookup{
        private $gift;
    
        public function giveGift(){
            $this->gift->select();
            $this->gift->pay();
        }
    
        public function setGift($instance){
            $this->gift = $instance;
        }
    }        
    $paoniu = TopFactory::all_Agents_in_one();
    $paoniu->giveGift();    

    聪明的我们继续明显的发现,虽然多了个TopFactory 来帮我们处理了众多的set问题,但是每个主类还要配一个总代类TopFactory ,这也是很大的工作,需要一种更高级的方案,让程序员的生活变得Easier一些。

    IoC容器

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

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

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

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

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

    //把所有类注册到工厂
    class Factory {
        static function BuyGifts(){
            return new BuyGifts();
        }
        static function Hookup(){
            return new Hookup();
        }
        static function Di(){
            return new Di();
        }
    }
    //全局的容器类
    class Di {
        protected $binds;
        protected $instances;
        public function bind($key, $concrete)
        {
            if ($concrete instanceof Closure) {
                $this->binds[$key] = $concrete;
            } else {
                $this->instances[$key] = $concrete;
            }
        }
        public function make($key, $parameters = [])
        {
            if (isset($this->instances[$key])) {
                return $this->instances[$key];
            }
            array_unshift($parameters, $this);
            return call_user_func_array($this->binds[$key], $parameters);
        }
    }
    //主类
    class Hookup{
    
        public function giveGift($gift){
            $gift->select();
            $gift->pay();
        }
    
    }
    /*初始化各资源开始*/
    $di = Factory::Di();
    //绑定次类
    $di->bind('BuyGift', function(){
        return Factory::BuyGift();
    });
    //绑定主类到容器
    $di->bind('Hookup', function(){
        return Factory::Hookup();
    });

    /*初始化各资源完毕*/

    //得到主类的实例
    $paoniu = $di->make('Hookup'); //得到次类的实例 $gift = $di->make('BuyGift'); $paoniu->giveGifts($gift);
    //请注意的是,我们在绑定Ioc容器的时候,是这样写的:
    
    $di->bind(‘BuyGift’, function(){
        return Factory::BuyGift();
    }
    
    //而没有这样写:
    
    $di->bind(‘BuyGift’, Factory::BuyGift());
    
    //第一个参数是数组的键名,第二个参数是数组的值;
    //第一种写法用了回调函数,它只有在读取数组的值的时候才会被执行;
    //第二种方法直接实例化成一个对象,保存到数组的值中。第二种方法会比较占资源。
    //我们绑定的是别名,所以绑定的类名改了也不会影响到主类的使用,只需要修改类绑定代码甚至只修改工厂类代码

    1、我们把所有的类注册到了工厂Factory,方便我们不用麻烦的new。

    2、我们把所有的类绑定到容器Di,而不是实例化,而当我们调用Di的make方法时,才会进行实例化操作。

    所以,对于框架而言,我们在初始化的时候,就可以进行1和2的操作,这并不会占用多少资源,因为没有实例化,只是进行了绑定和注册的操作,相当于配置了映射。

    而只有我们需要用的这些类的时候,我们才进行$di->make()操作,返回该类的实例。至此,我们整个框架的类资源都可以随身调用而不用new或者引入,而且不用担心耗资源的问题,大大提高开发灵活性和降低程序的耦合性。

     加入面向接口interface编程思想

    /**
     * Interface Buy
     * 这里引入面向接口编程思想
     * 假如买礼物这个步骤有多种实现方法,就好像用数据库可以有mysql,redis等方式,
     * 无论用什么方式,这个买礼物,就是一个接口interface,实现这个接口的class可以有很多。
     */
    interface Buy{
        public function buy();
    }
    
    /**
     * Class BuyGifts
     * 这是实现接口的一种class
     */
    class BuyGifts implements Buy{
        public function buy(){
            echo "buygift";
        }
    }
    
    //把所有类注册到工厂
    class Factory {
        static function BuyGifts(){
            return new BuyGifts();
        }
        static function Hookup(){
            return new Hookup();
        }
        static function Di(){
            return new Di();
        }
    }
    //全局的容器类
    class Di {
        protected $binds;
        protected $instances;
        public function bind($key, $concrete)
        {
            if ($concrete instanceof Closure) {
                $this->binds[$key] = $concrete;
            } else {
                $this->instances[$key] = $concrete;
            }
        }
        public function make($key, $parameters = [])
        {
            if (isset($this->instances[$key])) {
                return $this->instances[$key];
            }
            array_unshift($parameters, $this);
            return call_user_func_array($this->binds[$key], $parameters);
        }
    }
    //主类
    class Hookup{
    
        public function giveGift(Buy/*这里加入接口强制类型*/ $gift){
            $gift->select();
            $gift->pay();
        }
    
    }
    /*初始化各资源开始*/
    $di = Factory::Di();
    //绑定次类
    $di->bind('BuyGift', function(){
        return Factory::BuyGift();
    });
    //绑定主类到容器
    $di->bind('Hookup', function(){
        return Factory::Hookup();
    });
    
    /*初始化各资源完毕*/
    
    //得到主类的实例
    
    $paoniu = $di->make('Hookup');
    //得到次类的实例
    $gift = $di->make('BuyGift');
    
    $paoniu->giveGifts($gift);
  • 相关阅读:
    百度生成短网址
    虚拟机开发配置
    WAMPserver配置(允许外部访问、phpmyadmin设置为输入用户名密码才可登录等)
    CSS3阴影 box-shadow的使用和技巧总结
    HTML5日期输入类型(date)
    Ubuntu18设置mysql的sql_mode
    php图片压缩-高清晰度
    微信小程序禁止下拉_解决小程序下拉出现空白的情况
    使用命令行设置MySql编码格式
    腾讯首页分辨手机端与pc端代码
  • 原文地址:https://www.cnblogs.com/zenghansen/p/5584332.html
Copyright © 2020-2023  润新知