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肯定会原!地!爆!炸!(气急败坏)的!奖金从此无望事小,老王心怀鬼胎事大啊!
问题二:
生产汽车的脚本(就是你上面写的那个类)被派送到全国各个工厂,又指派三四个运维同事,不远万里到工厂没日没夜安装维护,终于产出了第一批汽车。老王定睛一看,不行啦,车标“王”怎么给忘记了呢?这时候脚本必须更换了!对你来说可能是加一行代码,但是对整个系统流程(包括运维哥们的辛苦工作)影响是极其不好的。
问题三:
老王说,其实他随他母亲姓,老爹其实是姓“宋”。所以想生产一批“宋”字标的汽车。所以你那个脚本。。。
嗯,正经的总结一下上面三个问题:
- 依赖严重;
- 扩展不方便;
- 更改不方便;
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);
}
}
- 嗯,看起来还不错。我们只是new了一个工厂实例就可以搞定了刚刚的那一大堆乱麻(
依赖),依赖警报(问题一)解除了。 - 我们不会再在代码里写死了需要哪些‘零件’,而是通过构造函数穿参的方式,让外部来决定有什么零件。方便了扩展(问题二迎刃而解)。
- 同第二条,你要生产“宋”字标,那就在构造函数参数中加上“宋”标识的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也不爱我了,现在还要去看运维兄弟的脸色,前途胸险呐。你只是下面的这颗小豌豆,梦想就是打打小僵尸,收获一点阳光。但事实上,僵尸头上都套着水桶,夜晚也没有阳光。
事实上我们也清楚,工厂中的代码不可能(也不应该)经常改动,但是增加‘宋’车标这样的需求又不得不做。怎么办呢?如果工厂提供一个方法,可以由外部来传入类似‘case 'Sign_song' : return new Sign_song();’这样的内容就好了。这样,程序在初始化的时候,工厂应该是空的,一切都需要外部注入(laravel称这过程叫bind,注册或绑定)。OK,你应该知道了,我下面要讲重要的东西了--Larvel中的IoC容器。(麻烦中间玩手机的同学,告诉下后排看片的同学声音小点,别吵到了前排睡觉的同学!)
3、Laravel服务容器
Laravel官方文档其实更多的是教人如何使用,原理性的东西一概省略。这就让我们这些优秀的工程师很是摸不着头脑,每一步都要自己踩实了走,每一个问题都要自己探索。
几个注意的点:
- php的一个基础函数call_user_func_array,自己百度,看懂了再继续;
- php的回调函数(或匿名函数,闭包),自己百度,看懂了再继续;
- 没了;
不多说了,直接上代码。这十行代码看懂了,你将获益无穷。
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。这一次老王没有打你耳光,你完成的很好。虽然是周六,但大家都加班呢。公司的事儿也都按计划顺利进行着。老王早早的就下了班,好像回家去了,不过你还得呆到下班时间才能回家。
虽然我也就是个小豌豆,但收获了阳光的我,也能变成这样呢!哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒......
纳尼?你不赞一个?☹️哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒......