在开始以前要明确一个概念,不论是设计模式,仍是依赖注入等等,都是为了实现模块化.所谓模块化就是但愿一个软件是由不少子模块组成的,这些模块之间的依赖程度尽可能的低,也就是若是系统中不须要某一个功能,那么只要移除这个功能所对应的模块就能够了.php
那么,咱们今天要说的服务容器就是为了实现上面的功能.你应该听过,Laravel中的服务容器其本质上是一个IoC容器,可是好像队IoC又不是很了解,讲来说去优势不少,功能很强劲.可是不懂原理怎么用都不踏实啊.因此,这里咱们本身来实现一个IoC容器,洞察其本质.编程
在开始以前,先说明一点,阅读本篇文章至少要保证有一下的基础知识:设计模式
php反射用法数组
闭包的use用法闭包
若是不懂上面的内容,请先补充.避免阅读代码时候产生的不适感.app
<?php /** * Created by PhpStorm. * User: jiayao * Date: 2016/9/1 * Time: 21:41 */ class Container { public $binding = []; /** * @param $abstract * @param null $concrete * @param bool $shared */ public function bind($abstract, $concrete = null, $shared = false) { if (!$concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->binding[$abstract] = compact('concrete', 'shared'); } protected function getClosure($abstract, $concrete) { return function ($c) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete); }; } /** * @param $abstract * @return object * */ public function make($abstract) { $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } return $object; } /** * @param $concrete * @param $abstract * @return bool */ public function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; } /** * @param $abstract * @return mixed */ protected function getConcrete($abstract) { if (!isset($this->binding[$abstract])) { return $abstract; } return $this->binding[$abstract]['concrete']; } /** * @param $concrete * @return object */ public function build($concrete) { if($concrete instanceof Closure) { return $concrete($this); } //反射... $reflector = new ReflectionClass($concrete); if(!$reflector->isInstantiable()) { echo $message = "Target [$concrete] is not instantiable"; } //获取要实例化对象的构造函数 $constructor = $reflector->getConstructor(); //没有定义构造函数,只有默认的构造函数,说明构造函数参数个数为空 if(is_null($constructor)) { return new $concrete; } //获取构造函数所须要的全部参数 $dependencies = $constructor->getParameters(); $instances = $this->getDependencies($dependencies); //从给出的数组参数在中实例化对象 return $reflector->newInstanceArgs($instances); } /** * @param $paramters * @return array * 获取构建类所须要的全部依赖,级构造函数所须要的参数 , */ protected function getDependencies($paramters) { $dependencies = []; foreach ($paramters as $paramter) { //获取到参数名称. $dep = $paramter->getClass(); if(is_null($dep)){ $dependencies = null; }else{ $dependencies[] = $this->resolveClass($paramter); } } return (array)$dependencies; } /** * @param ReflectionParameter $parameter * @return object * 实例化 构造函数中所须要的参数. */ protected function resolveClass(ReflectionParameter $parameter) { $name = $parameter->getClass()->name; return $this->make($name); } }
这就是一个IoC容器的实现代码.乍一看,很麻烦.其实真的蛮麻烦的 =_=,若是是第一次接触的话,并非那么好消化,这里再给出使用IoC容器的代码框架
<?php /** * Created by PhpStorm. * User: jiayao * Date: 2016/9/1 * Time: 21:37 */ require __DIR__ . '/Container.php'; interface TrafficTool { public function go(); } class Train implements TrafficTool { public function go() { echo "train...."; } } class Leg implements TrafficTool { public function go() { echo "leg.."; } } class Traveller { /** * @var Leg|null|Train * 旅行工具 */ protected $_trafficTool; public function __construct(TrafficTool$trafficTool) { $this->_trafficTool = $trafficTool; } public function visitTibet() { $this->_trafficTool->go(); } } //实例化IoC容器 $app = new Container(); //绑定某一功能到IoC $app->bind('TrafficTool', 'Train'); $app->bind('travellerA', 'Traveller'); // 实例化对象 $tra = $app->make('travellerA'); $tra->visitTibet();
运行例子发现会输出:train..
.这个例子假设旅行者去青藏旅行,能够坐火车(train)或者走路(leg)去青藏.模块化
好了,其实这样子本篇文章就能够结束了,由于全部的答案都在IoC容器的实现中, 可是为了能够更好的理解上面的代码,咱们继续往下分析.函数
首先,但愿你能够运行一下上面的代码,虽然简单的运行代码并不会帮助你理解代码,可是一个能够运行的例子会让人比较踏实,可以更有把握的理解代码.工具
在深刻每一行代码以前,咱们从总体上来分析,IoC解决了一个什么问题?简单点说,就是咱们再实例化对象的时候不用使用new了,有了IoC容器以后,咱们调用make函数就能够实例化出一个对象了.然而,你发现,Traveller的构造函数是须要一个参数的,但是咱们好像并无提供这个参数?
这就是IoC强大之处了, 调用make实例化对象的时候,容器会使用反射功能,去分析咱们要实例化对象的构造函数,获取构造函数所需的每一个参数,而后分别去实例化这些参数,若是实例化这些参数也要参数,那么就再去实例化参数的参数.....=_=.到最后成功实例化咱们所须要的traveller了.在Container的build函数就是使用反射来实例化对象.
可是,有一个问题了,IoC容器怎么知道实例化Traveller的时候须要的参数train,而不是leg?
其实,IoC容器什么都不知道,IoC会实例化哪些对象都是经过bind
函数告诉IoC的,上面的例子两次调用bind函数,就是告诉Ioc能够实例化的对象有Train
和Traveller
. 再通俗讲就是:当须要当咱们须要TrafficTool
这个服务的时候去实例化Train
这个类,须要一个travellerA
的旅行者的时候去实例化Traveller
类.而Train
这个就是travellerA
就是去青藏的方式. 这样子若是想要走路去青藏的话只要把$app->bind('Visit', 'Train');
改成$app->bind('Visit', 'Leg');
就能够.
但是,这上面的这些有什么意义?直接$tra = new Traveller($trafficTool)
来实例化对象好像也没有什么很差的.
使用new来实例化对象的时候,会产生依赖.好比上面
$tra = new Traveller($trafficTool)
,这说明咱们要建立一个Traveller以前得有一个$trafficTool
,即Traveller
依赖于trafficTool
.当使用new来实例化Traveller
的时候,Traveller
和trafficTool
之间就产生了耦合.这样,这两个组件就没办法分开了.
而使用IoC是怎么解决这个问题的,以前说过,若是想要若是想要走路去青藏的话只要把$app->bind('Visit', 'Train');
改成$app->bind('Visit', 'Leg');
就能够.这样子,使用何种方式去青藏,咱们能够自由的选择.
咱们站在Laravel框架设计者的角度去想,设计者确定但愿一个框架提供的功能越多越好,可是又要保证强大的同时又不会限制使用者.最好能够保证使用者想实现什么奇怪的需求均可以.那么功能强大可是又不局限的最好方法就是什么都不作,提供一个强大的IoC容器.全部须要实现的功能都变成一个个服务,须要什么服务就把服务注册(即调用bind函数)到IoC中,而后让IoC去管理依赖.
开发者想到一个变态的需求:走路去青藏,那么只要你实现了走路去青藏这个功能,而后把这个功能当作一个服务注册到IoC中,之后你须要这个服务的时候IoC就帮你实例化这个服务.当开发者回归正常以后以为仍是坐火车去吧,因而不注册走路这个功能,实现坐火车的功能,而后注册这个功能.下次IoC实例化的时候就是实例化坐火车这个功能了.
好了,剩下的部分就是一行一行的阅读Container的代码了,Laravel框架中的服务容器代码也是这个样子,只是功能更增强悍.可是核心是同样的,上面的代码懂了之后再使用Laravel框架就会更加游刃有余了.
文章虽短.可是内容不少.尤为是代码,虽然可能只是短短的一个例子,可是包含了不少内容.值得好好分析,这里放个彩蛋:Traveller中构造函数参数相似为TrafficTool,是一个接口.可是实例化的是Train.这里体现了设计模式的一个原则
面对接口编程,而不是面对实现编程.