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

Laravel这个框架,用起来方便,理解起来不简单。

为什么不简单?因为包含了一大堆所谓“先进”的概念,其中依赖注入(DI)和Ioc容器是比较核心的内容之一。

我百度了一下,讲PHP DI和Ioc的内容很少,更别说详解Laravel ioc的了。

在这里,我综合了几篇写得比较典型的文章,以一个产品经理的身份,从用户体验的角度尝试让初学者也能比较容易理解这个2个概念。

 

DI和Ioc就是一种设计模式,这个概念已经存在10几年了,请先对面向对象编程和设计模式(DesignPatterns)有初步的理解:

预先了解一下这三种模式:1. 工厂模式 2. 单例模式 3. 注册器模式

也请了解一下 interface 的用法和作用;

 

如果上述内容你都不是很熟悉,没关系,很简单的,请接着看:

 

1. 首先来看 依赖注入(DI):

故事是这样的:

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

 

class Hookup {

 

public function getPhone(){

     //此处省略

}

 

public function inviteHer(){

     //此处省略

}

 

public function giveGifts(){

     //此处省略

}

public function kissHer(){

     //此处省略

}

}

比方说

这个类是什么意思?Hookup就是泡妞的意思,里面有4个步骤,第一步打电话,第二部邀请她共进晚餐,第三步送礼物,第四步打波儿,第五步.....我只能帮你到这里了...

 

别看只有4步,其实每一步都不简单,第一步打电话,你得先要到人家电话吧,第二步邀请吃饭,你得提前订好饭店吧,第三步送礼物,你得先买礼物吧~第四步Kiss,你总得抓住一个合适的机会吧;

 

当你在专心处理Hookup的时候,我太了解你了,你只关心结果,也就是抱得美人归,问电话号码,订餐,买礼物,找机会这种小事,就交给其他“类”处理吧。

 

如下:

 require 'BuyGifts.php';

 

 

class Hookup {

 

public function getPhone(){

     //此处省略

}

 

public function inviteHer(){

     //此处省略

}

 

public function giveGifts(){

$gift = new BuyGifts();

$gift->select();

$gift->pay();

}

public function kissHer(){

     //此处省略

}

}

 

$paoniu = new hookup();

$paoniu->giveGifts();

 

这里面送礼物这个环节(比较懒,其他的不写了),就引入了BuyGifts这个类,买礼物这种小事,就交给BuyGifts这个秘书去处理吧。

 

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

 

Hookup这个可以称之为主类,BuyGifts称之为次类,为了实现主类的目标,要依赖很多次类来配合。

 

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

 

问题来了。现在讲正经的。

 

比方说你很忙,你不仅要泡妞,还要开公司,做生意,没事打打高尔夫球什么的。这每一件事都是一个“主类”,BuyGifts 这种“次类” ,除了泡妞,也可以放在 做生意这个类里面调用,做生意也要送礼的嘛,当然还可以应用在别的主类。 

 

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

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

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

 

怎么办: 工厂模式

工厂模式很简单,就是来帮助你批量实例化“次类”的。也就是说,用了工厂模式,你的主类中将不会再出现new这样的关键字。

 

看实例,有3个类 哦:

 

-----   Factory.php    ----------

 

include 'BuyGifts.php'

 

class Factory {

 

static function getPhone(){

 

return new getPhone;

}

 

 

static function BuyGifts(){

 

return new BuyGifts;

}

 

//……下面还有很多,此处省略

 

}

 

-----   BuyGifts.php    ----------

 

class  BuyGifts{

 

public function select(){

//此处省略 }

 

public function pay(){

//此处省略 }

 

}

 

-----   Hookup.php    ----------

 

require 'BuyGifts.php';

require 'Factory.php ';

 

class Hookup {

 

private $gift;

 

public function getPhone(){

     //此处省略

}

 

public function inviteHer(){

     //此处省略

}

 

public function giveGifts(){

$this->gift = Factory::BuyGifts();

$gift->select();

$gift->pay();

}

public function kissHer(){

     //此处省略

}

}

 

$paoniu = new hookup();

$paoniu->giveGifts();

 

 

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

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

 

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

 

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

 

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

 

-----   Hookup.php    ----------

 

require 'BuyGifts.php';

require 'Factory.php ';

 

class Hookup {

 

private $gift;

 

public function getPhone(){

     //此处省略

}

 

public function inviteHer(){

     //此处省略

}

 

public function giveGifts(){

$gift->select();

$gift->pay();

}

 

public function kissHer(){

     //此处省略

}

 

public function setGift($instance){

$this->gift = $instance;

}

}

 

$paoniu = new hookup();

$paoniu->setGift(Factory::BuyGifts()); // 看到 Factory已经滚粗 我们的主类了

$paoniu->giveGifts();

 

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

 

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

 

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

 

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

里面:

public function setGift($instance){

$this->gift = $instance;

}

public function setPhone($instance){

$this->phone = $instance;

}

 

public function setInvitation($instance){

$this->Invitation = $instance;

}

 

….

 

外面:

 

$paoniu->setGift(Factory::BuyGifts());

$paoniu->setPhone(Factory::GetPhone());

$paoniu->setInvitation(Factory::SendInvitation());

 

….

 

 

这个时候,你已经把Factory 赶出 Hookup公司了,所以,公司内部的事情,只能你自己搞定了。

公司外部的业务,这时Factory中介 又屁颠屁颠的跑过来找你,提出一个方案:

 

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

 

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;

}

 

}

 

黑中介Factory对你说:“你看我搞了个总代公司(TopFactory),你在外面也不要new啊,set什么的了,我全包了。

 

于是现在你在外面要做的变简单了:

 

$paoniu = TopFactory::all_Agents_in_one();

$paoniu->giveGifts();//giveGifts里面就可以调用Buygifts了;

$paoniu->getPhone();

….

 

 

到现在为止,你已经有一套完整的 依赖注入方案了,这个方案组建的时候虽然有点复杂(层层转包),但是一旦建好,维护起来比较方便。

 

可是,别忘了公司内部的事情还没解决哪,另外要提醒你,你是程序员,不是老板,所以Factory那一部分也是你的活~~~

 

 

需要一种更高级的方案,让程序员的生活变得Easier一些。

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

 

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

 

 

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

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

 

 

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

 

看实例:

———Di.php——-

class Di {

 

protected static $objects;

 

static function set($key, $object){

 

self::$objects[$key]=$object;

 

}

 

static function get ($key){

 

return self::$objects[$key];

 

}

 

static function unset ($key){

 

unset(self::$objects[$key]);

 

}

 

 

}

 

 

-----   Hookup.php    ----------

 

require ‘BuyGifts.php';

require 'Factory.php ';

require 'Di.php ';

 

class Hookup {

 

private $di;

 

function __construct(Di &$di){

       $this->_di = $di;

    }

 

public function getPhone(){

     //此处省略

}

 

public function inviteHer(){

     //此处省略

}

 

public function giveGifts(){

$this->di->get(‘BuyGifts’)->select();

$this->di->get(‘BuyGifts’)->pay();

}

 

public function kissHer(){

     //此处省略

}

 

public function setGift($instance){

$this->gift = $instance;

}

}

 

$di = new Di();

$di->set(‘BuyGift’, function(){

return Factory::BuyGift();

}

 

 

$paoniu=new Hookup($di);

$paoniu->giveGifts();

 

 

———-

 

可以看到,IoC模式才是真正的总代,它连公司内部的事情也管了。和设立set部门相比,set只能处理一个个单独的业务,比如setGift,setPhone,setInvitation,之类的,,而ioc容器把这些实例全部放在一个数组里统一处理,并且还可以重新命名(键名),实现了完全的解耦。

 

 

 

请注意的是,我们在注册Ioc容器的时候,是这样写的:

$di->set(‘BuyGift’, function(){

return Factory::BuyGift();

}

 

而没有这样写:

$di->set(‘BuyGift’, Factory::BuyGift());

 

 

第一个参数是数组的键名,第二个参数是数组的值;

 

第一种写法用了回调函数,它只有在读取数组的值的时候才会被执行;

第二种方法直接实例化成一个对象,保存到数组的值中。第二种方法会比较占资源。

另外我们发现

$di = new Di();

$di->set(‘BuyGift’, function(){

return Factory::BuyGift();

}

 

 

$paoniu=new Hookup($di);

$paoniu->giveGifts();

 

 

这里面怎么还有new呢,我们用静态方法和Factory写得优雅一些:

Di::set(‘BuyGift’, function(){

return Factory::BuyGift();

} //注册到容器

$paoniu=factory::Hookup(factory::DI()); // 将容器注入主类

$paoniu->giveGifts(); //这里就可以调用BuyGift()这个次类(依赖类);

 

 

 

好了,我们来看看Laravel Ioc容器 是怎么用的:

 

绑定类到容器

 

App::bind('foo', function()

{

    return newFooBar;

});

 

App 就是Ioc容器,bind就是set方法,’foo’ 就是数组中 实例的 键名;

 

 

至此,我们已经对Ioc容器有了一个比较清楚的认识,我们已经了解了工厂模式,注册模式,依赖注入等知识,其实依赖注入有3种方式,,一种就是set注入(单类注入),一种是容器注入(Ioc),还有一种是接口注入(Interface);前两种我们已经学习过了,后一种放在本文的第二部分来讲。

 

在本文的第二部分,我们将详细讲解LaravelIoC的用法,为什么说Laravel理解起来比较难,因为它的设计不是入门级别的,而是专业级别的,比前面讲的要复杂许多,但是看官方文档,寥寥数语,真有种想砸电脑的冲动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值