详解依赖注入(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);
原文地址:https://www.cnblogs.com/zenghansen/p/5584332.html