Laravel中的设计模式(二)——IoC容器

1、什么是IoC容器

在laravel的官方文档中( https://laravel.com/docs/5.4/container ), 它说:

Laravel 服务容器是管理类依赖和运行依赖注入的有力工具。依赖注入是一个花俏的名词,它实质上是指:类的依赖通过构造器或在某些情况下通过「setter」方法进行「注入」。

嗯,我知道你看不懂,因为我一开始也看不懂。不过一会儿你回过头来再看就会懂了。

汽车和其零件,是依赖关系

下面写点可能错误一大堆的代码,便于理解。

class Car {//小汽车
}

有一天老王(对,就是你隔壁那个)突然觉得造汽车很赚钱,决定说干就干...

但是造汽车需要什么呢?随便举例几个吧。

class Wheel {//轮子
}
class Engine {//发动机
}
class Seat {//座椅
}

所以造小汽车需要依赖 轮子、发动机、座椅。So, 小汽车的Class需要写成酱紫(这样子):

class Car {//小汽车
    private $components = [];//零件
    
    public function __construct()
    {
        $this->components[] = new Wheel();
        $this->components[] = new Engine();
        $this->components[] = new Seat();
        //...
        //...
        //...(汽车的零件很多的)
        //...
    }
}

问题一:
当然,你别指望汽车只需要几个零件就可以搞定了。如果这里有几百的零需要你来new的话,你会写几百行吗?你真的这样写的话,你的leader肯定会 原!地!爆!炸!(气急败坏)的!奖金从此无望事小,老王心怀鬼胎事大啊!

问题二:
生产汽车的脚本(就是你上面写的那个类)被派送到全国各个工厂,又指派三四个运维同事,不远万里到工厂没日没夜安装维护,终于产出了第一批汽车。老王定睛一看,不行啦,车标“王”怎么给忘记了呢?这时候脚本必须更换了!对你来说可能是加一行代码,但是对整个系统流程(包括运维哥们的辛苦工作)影响是极其不好的。

问题三:
老王说,其实他随他母亲姓,老爹其实是姓“宋”。所以想生产一批“宋”字标的汽车。所以你那个脚本。。。

嗯,正经的总结一下上面三个问题:

  1. 依赖严重;
  2. 扩展不方便;
  3. 更改不方便;

What?Why?How?

作为一个优雅的 码农(软件工程师),‘new’这个关键字是不可以出现的。

‘啪..’的一声,老王一耳光打在你左脸上。我造车没有轮子,没有发动机怎么造?快滚去把那几个客户搞定咯,不然....哼!

你很需要轮子和发动机,但是有不准‘new’。你正愁眉苦脸的使劲儿想办法(僵尸看了看你的脑子,嫌弃的走开了,旁边的屎壳郎却眼前一亮),突然跳了起来,对呀!我怎么没想到呢?

既然我没办法,我可以把这个挠头的问题扔给别人做啊!!!

WTF?这也可以吗?哒哒哒(敲黑板),下面我们讲第二章,工厂模式。

2、工厂模式

那个麻烦的‘new’既然老板不想看,我们可以悄悄的用,不让老板看到就行了嘛!‘new’的这件事情就交给‘工厂’去做好了。嗯。。最后再把‘工厂’藏在代码逻辑底层(一般像是框架源代码咯)。出了问题就找‘工厂’负责咯,跟我没关系。嘻嘻,想想还有点小激动呢。Perfect!

class Factory {//我是 '工厂'

    //告诉我你想要的($name),我就可以给你了
    public function makeComponent ($name) {
        switch ($name) {
            case 'Wheel' : return new Wheel();
            case 'Engine' : return new Engine();
            case 'Seat' : return new Seat();
            //...
            //...
        }
    }
}

嗯,现在再来看看我们的汽车类。

class Car {//小汽车
    private $components = [];//零件
    
    public function __construct()
    {
        $factory = new Factory();
        $this->components[] = $factory->makeComponent('Wheel');
        $this->components[] = $factory->makeComponent('Engine');
        $this->components[] = $factory->makeComponent('Seat');
        //...
        //...
        //...(汽车的零件很多的)
        //...
    }
}

但是你能把构造函数写成下面这样的话,也许会得到奖赏的呢(这样僵尸才会对你的脑袋感兴趣,而不是屎壳郎)!

public function __construct($component = [])
    {
        $factory = new Factory();
        foreach ($component as $item) {
            $this->components[] = $factory->makeComponent($item);
        }
    }
  1. 嗯,看起来还不错。我们只是new了一个工厂实例就可以搞定了刚刚的那一大堆乱麻(依赖),依赖警报(问题一)解除了。
  2. 我们不会再在代码里写死了需要哪些‘零件’,而是通过构造函数穿参的方式,让外部来决定有什么零件。方便了扩展(问题二迎刃而解)。
  3. 同第二条,你要生产“宋”字标,那就在构造函数参数中加上“宋”标识的key啊(可能你设定的是‘Sign_song’?所以$component=['Sign_song','Wheel'...])。如果还是不懂的话,我在给你写个使用说明书好了?总之问题三解决了。

嗯,有没有感觉升职加薪当上主管出任CEO迎娶白富美走上狗生巅峰的梦想就快要实现了?想想还有点......

'哆哆...'的皮鞋声打断了你的沉思。谁能把皮鞋穿出这种快节奏还真是厉害了啊。

‘啪..’的一声,老王一耳光打在你脸上(这次你很感谢他打的是右脸,强迫症的你终于感觉对称了)。小刘(陈?李?周?张?)啊,我说要‘宋’车标的,可是根本没这个标识啊!给你三天时间,不然....哼!

老王走后你还晕了一阵子,终于明白了他在说什么。在我的工厂中,根本没有‘Sign_song’这个case啊。所以好像要增加一个class,而且在工厂里也需要增加的。你通宵加班后写出了如下代码:

class Sign_song {
    //如何制造‘宋’字车标的方法(绝密)
}
class Factory {

    public function makeComponent ($name) {
        switch ($name) {
            //...以前的什么轮子,座椅,发动机
            //下面这行是你加班增加的
            case 'Sign_song' : return new Sign_song();
            //...
            //...
        }
    }
}

‘呼...’你长舒一口气。终于搞定了。客户难搞,老板难搞,产品难搞,连测试mm也不爱我了,现在还要去看运维兄弟的脸色,前途胸险呐。你只是下面的这颗小豌豆,梦想就是打打小僵尸,收获一点阳光。但事实上,僵尸头上都套着水桶,夜晚也没有阳光。

image

事实上我们也清楚,工厂中的代码不可能(也不应该)经常改动,但是增加‘宋’车标这样的需求又不得不做。怎么办呢?如果工厂提供一个方法,可以由外部来传入类似‘case 'Sign_song' : return new Sign_song();’这样的内容就好了。这样,程序在初始化的时候,工厂应该是空的,一切都需要外部注入(laravel称这过程叫bind,注册或绑定)。OK,你应该知道了,我下面要讲重要的东西了--Larvel中的IoC容器。(麻烦中间玩手机的同学,告诉下后排看片的同学声音小点,别吵到了前排睡觉的同学!)

3、Laravel服务容器

Laravel官方文档其实更多的是教人如何使用,原理性的东西一概省略。这就让我们这些优秀的工程师很是摸不着头脑,每一步都要自己踩实了走,每一个问题都要自己探索。

几个注意的点:

  1. php的一个基础函数call_user_func_array,自己百度,看懂了再继续;
  2. php的回调函数(或匿名函数,闭包),自己百度,看懂了再继续;
  3. 没了;

不多说了,直接上代码。这十行代码看懂了,你将获益无穷。

class Container {//超级工厂,换个名字叫‘容器’
    private $binds = [];//保存我们的脚本

    //上面说的,接受脚本的函数
    public function bind ($key, $callback) {
        if ($callback instanceof Closure) {
            $this->binds[$key] = $callback;
        }
    }
    //真正执行脚本的函数
    public function make ($key, $component = []) {
        if (is_array($key)) {
            $instance = [];
            foreach ($key as $item) {
                $instance[] = call_user_func_array($this->binds[$item], [$this, $component]);
            }
            return $instance;
        }
        return call_user_func_array($this->binds[$key], [$this, $component]);
    }
}

超级工厂建好了,哒哒哒.....开始生产汽车!和上面结合着看

//这个必须的
$container = new Container();

//程序启动后,分别绑定轮子,发动机,座椅,‘宋’标识等等脚本到容器中,告诉容器如何制造这些东西。简称服务注册。
$container->bind('Wheel', function () {
    return new Wheel;
});
$container->bind('Engine', function () {
    return new Engine;
});
$container->bind('Seat', function () {
    return new Seat;
});
$container->bind('Sign_song', function () {
    return new Sign_song;
});

//诶,这个是制造汽车的脚本。
//有一次递归,有点绕,但很关键,仔细看!!!
$container->bind('Car', function ($container, $component) {
    return new Car($container->make($component));
});

//生产一台‘宋’字标的汽车
$myJiaKeChong = $container->make('Car', ['Wheel','Engine','Seat','Sign_song'...]);

上面代码的Container,和bind的地方,其实都不会出现在你的业务代码里。你一般只需要写最后那一行,生产汽车的那一行(当然车也是老王的),就可以了。Laravel中,绑定bind这件事儿一般都在 app/Providers 目录下做。当然,你也可以在 bootstrap/app.php 文件中看到几个关键服务的绑定过程。

Laravel的容器当然比我上面的半吊子容器牛逼多了,你可以在 ~/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php 中,看到这个承载了Laravel一切的神秘容器。里面同样有bind和make方法,意义和用法同我的容器也是一样的。

OK。这一次老王没有打你耳光,你完成的很好。虽然是周六,但大家都加班呢。公司的事儿也都按计划顺利进行着。老王早早的就下了班,好像回家去了,不过你还得呆到下班时间才能回家。

虽然我也就是个小豌豆,但收获了阳光的我,也能变成这样呢!哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒......

image

纳尼?你不赞一个?☹️哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts ...
    layjoy阅读 8,603评论 0 121
  • 必备品文档:DocumentationAPI: API Reference视频:Laracasts速查表:Lara...
    ethanzhang阅读 5,739评论 0 68
  • 单体模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一变量进行访问。 单体模式的优点是...
    JSUED阅读 505评论 0 0
  • 一、MyBatis简介 1、MyBatis的优点 消除了大量的JDBC冗余代码 低学习曲线 友好的Spring集成...
    慕凌峰阅读 382评论 0 1
  • 十月,大学里最好的闺蜜结婚了,早上六点出发去参加她的婚礼,在坐车要二十分钟才能到的乡间院落里,参加婚礼不过几十人,...
    达达场阅读 237评论 0 0