详解依赖注入(DI)和Ioc容器
Posted zenghansen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解依赖注入(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或者引入,而且不用担心耗资源的问题,大大提高开发灵活性和降低程序的耦合性。
以上是关于详解依赖注入(DI)和Ioc容器的主要内容,如果未能解决你的问题,请参考以下文章