【Laravel—核心源码解析】依赖注入、控制反转、服务容器、服务提供者

前言 想要看懂源码首先得明白源码中的主要思想,不然很难理解源码中的实现过程,本章就着重讲解Laravel框架中的服务容器、依赖注入、控制反转、服务提供者
正文 首先我们举个例子
假设我们要开始一场旅行,以代码的方式来实现这场旅行,那既然是旅行肯定要选择交通工具,我们用代码这样实现:

//火车类 class Train { //买票 function buyTickets() { } //出发 function go() { } } //旅行类 class Travel { //出发 function go() { //选择火车 $train = new Train(); $train->buyTickets(); $train->go(); } }

我们通常都是以这种方式实现代码的结构,清晰、方便实现,但是如果我们又要旅行一次但这次我们不坐火车想坐飞机
那这时我们就需要修改旅行类的go方法(调用者),将实例化火车这个步骤修改为实例化飞机然后go~
//飞机类 class Aircraft { //买票 function buyTickets() { }//出发 function go() { } } //旅行类 class Travel { //出发 function go() { //选择飞机 $train = new Aircraft(); $train->buyTickets(); $train->go(); } }

这种情况我们的调用者(旅游类)就依赖于被调用者(火车,飞机类)变成了强耦合,如果我们要旅行好几次,或者说我们不止是旅行,我们出差时也要用到这些交通工具相关的类,那我们再把火车类更换为飞机类的时候是不是需要修改很多调用者呢?
工厂模式
这时我们为了避免这种情况发生,引入了工厂类
//创建工厂类 class TrafficFactory { public function getInstance() { //第一次旅行使用火车 //$tool = new Train(); //第二次旅行使用飞机 $tool = new Aircraft(); return $tool; } } //旅行类 class Travel { //出发 function go() { //获取工厂类 $factory = new TrafficFactory(); //获取这次的交通工具 $tool = $factory->getInstance(); //买票 $tool->buyTickets(); //出发 $tool->go(); } }

这下我们不管旅行几次换成什么交通工具我们都只需要去修改工厂类就可以了,而且我们的调用者也不再依赖于被调用者
【【Laravel—核心源码解析】依赖注入、控制反转、服务容器、服务提供者】但实际上需求总是那么的可怕,这次要新增的交通工具是汽车,自驾不需要买票(buyTickets),但是要自己去加油,我们需要先创建汽车类
class Car { function gasUp() { } function go() {} }

我们一样可以将汽车类加到工厂类中,但是我们在使用时并不知道工厂给我们的是需要买票的飞机和火车还是需要加油的汽车
接口
这时就引入的接口类
interface Traffic { //准备,不管有什么前置使用条件(加油,买票)都需要再次方法进行实现 public function ready(); //出发 public function go(); }

所有有关交通工具的类都需要遵守规则实现接口里的方法,所以进行以下改造(这里为了不贴太多代码我们只改造汽车类)
class Car implements Traffic { function ready() { $this->gasUp(); } function gasUp() { } function go() { } }

这样我们就将交通工具的使用方法给统一了
现在我们的调用者不直接依赖于被调用者,但是我们却依赖于工厂类了,有什么办法这个调用者与工厂进行解耦呢?
依赖注入(DI)&控制反转(IOC)
调用者不需要去关心用的是什么交通工具,也不需要知道要做什么准备我们只管go,以依赖注入&控制反转的思想进行如下改造
class Travel { //出发 function go(Traffic $tool) { //出发 $tool->go(); } } //获取交通工具工厂类 $factory = new TrafficFactory(); //获取这次的交通工具 $tool = $factory->getInstance(); $travel = new Travel(); //准备 $tool->ready(); $travel->go($tool);

这下调用者只管go,外面做什么他都不用管,就算是更换了其它交通工具甚至是更换了工厂也不关调用者的事了。
起先我们获取交通工具是在调用者中做的,现在放到外部去了,这时控制权就不在调用者内部这种变化称为控制反转,我们以传参的形式将调用者所需的服务(交通工具)注入给调用者使用,这种行为称为依赖注入
接下来随着业务增多,需求不断变化(上面的例子全是交通工具相关,旅行还会涉及用餐,住宿等等),因为工厂类通常职责专一(也是面向对象的主要思想之一)我们需要的工厂也会越来越多工厂类变得越来越多
再者上面提到的交通工具,用餐,住宿并不只适用与旅行,我们还可能出差等等,我们在实现这些功能时我们都需要提前准备好调用者所需的依赖
有没什么办法可以改进上面两点呢?
服务容器(IOC容器)
这时服务容器就出现了,我们将上述例子中的交通工具,用餐,住宿统称为服务,将服务统一收纳起来,存放的地方就是服务容器,可以简单理解为在框架启动时将以上服务提前存入服务容器中的数组中,在使用时去数组中获取,下面进行简单的实现
//-------------框架启动开始------------- //服务容器 class Container { //存放位置 protected $bindings; //对服务进行绑定 public function bind($abstract, $concrete) { $this->bindings[$abstract] = $concrete; } //制造服务 public function make($abstract) { return $this->bindings[$abstract]; } } //创建容器 $container = new Container(); //以闭包的形式(后续会将为何闭包)注册服务 $container->bind('tool', function(){ //以汽车为例 return new Car(); }); //-------------框架启动结束-------------class Travel { //出发 function go(Traffic $tool) { //出发 $tool->go(); } } //获取交通工具工厂类 $tool = $container->make('tool'); $travel = new Travel(); //闭包在使用的时候要先进行调用所以这里是$tool()而不是直接将$tool传入 $travel->go($tool());

这样我们只需要在框架启动过程中就将所有可能用到的服务进行注册即可,使用时再去容器中获取
在上面在对服务进行绑定的时候使用了闭包的写法避免了在绑定的过程中直接对类进行实例化,这样就减少了框架启动时的开销
具体的原理在这篇文章有提到:PHP Closure::bind方法理解及使用
上面举得例子只是对交通工具进行注册,但如果进行注册的服务有很多就会有一大堆服务杂乱无章的堆积在框架启动时的地方
服务提供者
这时服务提供者就来了
服务容器是将具体的服务进行注册收纳及解析使用,通常一些不集中的服务都是通过在框架各个位置进行注册,而服务提供者则是将与自身相关或依赖的服务进行集合来注册,类似交通工具,住宿之类的服务都会创建与之相关的交通工具服务提供者,住宿服务提供者,在具体的服务提供者类内部进行集合注册,和一些前置处理,好处就是相关的服务可以用放在一起进行集中管理
有没发现这样又有点像是工厂模式了,创建相关的服务提供者就像创建相关的工厂一样。其实项目演变就是这样我们抛弃了相对较小工厂模式选择了较大的“超级工厂”服务容器,服务容器壮大的同时又需要服务提供者来进行分类管理。
下面进行简单实现
//交通工具服务提供者 class ToolServiceProvider { //统一注册 public function register() { //创建容器 $container = new Container(); $container->bind('tool', function () { //以汽车为例 return new Car(); }); } } //交通工具服务提供者 $toolServiceProvider = new ToolServiceProvider(); $toolServiceProvider->register(); //用餐服务提供者(省略代码未贴具体实现代码) $eatServiceProvider = new EatServiceProvider(); $eatServiceProvider->register(); //-------------框架启动结束-------------class Travel { //出发 function go(Traffic $tool) { //出发 $tool->go(); } } //获取交通工具工厂类 $tool = $container->make('tool'); $travel = new Travel(); $travel->go($tool);

本篇文章只是进行思想解读,上述所有代码实现在实际运用场景中都不仅仅是这么简单,特别是服务容器与服务提供者,在Laravel框架底层中的实现过程还是较为复杂的,但在使用方面还是十分优雅,不会像上面的例子为了节省代码显得有点生搬硬套。
END

    推荐阅读