1,什么是IOC服务容器?
服务容器,顾名思义就是就是一种容器,在日常生活中,我们常说的容器一般是指装水的器皿。
而这里laravel说的容器,抽象出来也就是一种放置各种服务的容器,具体的服务则是指每一个类,比如说我们写入一个发送邮件的类
class SendMail{
public function send()
{
echo "发送邮件";
}
}
服务容器就是把多个这样的服务放在容器内。看到这里,大多数人肯定会说,容器这个概念不就是把每个类放在一个地方进行管理,那这么做的优点是什么?接下来我们讲讲服务容器IOC这个概念具体在我们的编码过程中起到什么作用?这里有一个应用场景,韩梅梅去西藏旅游,我们需要用代码的形式模拟出韩梅梅的出行,首先,去西藏韩梅梅可以选择三种交通方式,1,骑自行车,2,坐火车,3,乘飞机。先看我们用常规的方式写出韩梅梅用三种出行工具之一的代码:
interface Vehicle{
public function go();
}
class bike implements Vehicle{
public function go()
{
echo "通过自行车出游";
// TODO: Implement go() method.
}
}
class train implements Vehicle{
public function go()
{
echo "通过火车出游";
// TODO: Implement go() method.
}
}
class plane implements Vehicle{
public function go()
{
echo "通过飞机出游";
// TODO: Implement go() method.
}
}
class HanMeiMei{
private $vehicle;
public function __construct()
{
$this->vehicle = new bike();
}
public function travel()
{
$this->vehicle->go();
}
}
$HanMeiMei = new HanMeiMei();
$HanMeiMei->travel();
观察上面的代码,我们可以发现,韩梅梅的出行方式依赖HanMeiMei类构造函数内部new的对象,如果我们要修改韩梅梅出行方式,那么我们就要修改构造函数new的对象。这样在程序开发上就不符合低耦合的概念,虽然改动业务上的变化,免不了引起代码层面的变化,但是我们希望业务层改动,我们可以实现轻改动代码,于是面对这个版本我们可以使用工厂模式改进代码,代码如下:
interface Vehicle{
public function go();
}
class bike implements Vehicle{
public function go()
{
echo "通过自行车出游";
// TODO: Implement go() method.
}
}
class train implements Vehicle{
public function go()
{
echo "通过火车出游";
// TODO: Implement go() method.
}
}
class plane implements Vehicle{
public function go()
{
echo "通过飞机出游";
// TODO: Implement go() method.
}
}
class factory{
public function createVehicle($type)
{
switch ($type){
case "car":
return new train();
break;
case "bike":
return new bike();
break;
case "plane":
return new plane();
break;
default:
exit("类型错误");
break;
}
}
}
class HanMeiMei{
private $vehicle;
public function __construct($type)
{
$factory = new factory();
$this->vehicle = $factory->createVehicle($type);
}
public function travel()
{
$this->vehicle->go();
}
}
$HanMeiMei = new HanMeiMei('plane');
$HanMeiMei->travel();
从上面可以看到,从一开始的对交通工具的依赖,现在变为对工厂类的依赖,虽然可以实现只修改工厂$type的方式改变交通工具,但是,一旦增加交通方式的种类,还是免不了要去修改factory类的内部实现,所以,我们能不能再想出一种跟完善的方式去解决类和类之间的依赖?
我们可以提纯一开始的方式和后面使用工厂模式最主要的问题,我们可以发现无论是最传统的方式,还是工厂模式,其实就是对“new”关键字使用的地方。
2,laravel的IOC简单实现
<?php
class Container
{
protected $bindings = [];
public function bind($abstract, $concrete = null, $shared = false)
{
if (!$concrete instanceof \Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact("concrete", "shared");
//dd($this->bindings);
}
protected function getClosure($abstract, $concrete)
{
return function ($c) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
return $c->$method($concrete);
};
}
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof \Closure;
}
protected function getConcrete($abstract)
{
if (!isset($this->bindings[$abstract])) {
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
public function build($concrete)
{
if ($concrete instanceof \Closure) {
//dd(($concrete($this) 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();
$instance = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instance);
}
protected function getDependencies($parameters)
{
$dependencies = [];
foreach ($parameters as $parameter) {
$dependency = $parameter->getClass();
if (is_null($dependency)) {
$dependencies[] = null;
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array)$dependencies;
}
protected function resolveClass(\ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
class A{
public function test()
{
echo 333333;
}
}
$Container = new Container();
$Container->bind("mine",function (){
new A();
});
$car = $Container->make('mine');
$car->test();
通过上例可以看到,仅仅百十行代码,就可以实现ioc容器最核心的功能,解决依赖 注人的根本问题。在实现过程中,没有用new关键字来实例化对象,不需要人来关注组件 间的依赖关系,只要在容器填充过程中理顺接口与实现类之间的关系及实现类与依赖接口之 间的关系,就可以流水线式地完成实现类的实例化过程。这里类的实例化是通过反射机制完 成的,对此不清楚的读者可以查看PHP的重要性质这一章关于反射机制的介绍,当然也可 以在容器中直接填充实例化对象的回调函数,对于那些特殊的类可以设计特定的回调函数。在Laravel框架的官方文档中,将其称为服务容器,核心功能是IoC容器用以解决依赖注人, 而对服务容器的填充部分称为服务提供者,所以对于Laravel框架来说这种叫法更贴切,因 为框架中的Container类并不仅仅完成了 IoC容器的功能,还在程序运行过程中提供各种相 应的服务,包括对象、生成对象的回调函数、配置等。
为了方便理解IOC实现,流程图如下: