啥子是Ioc容器
当我们在使用或者阅读一些开发框架时,比如java里spring,php里laravel,yaf,都会提到DI和Ioc,拿laravel来说,laravel就是一个大的Ioc服务容器,管理类依赖和运行依赖注入,如果你第一次去看到或者使用这些思想,我反正是尼克扬问好脸,使用了一段时间并慢慢读了一些源码和网上帖子后,和大家分享一些理解。
Ioc(控制反转) && DI(依赖注入)
啥子是Ioc容器,我们先看两个名词,Ioc(控制反转)和DI(依赖注入),一开始看到这两个名词,我就想,这都啥子玩意,先来一起思考几个问题。
1、控制反转了,那原本是谁控制谁,为啥要反转。
2、依赖注入是什么依赖注入给谁。
开始扯犊子:
面向对象中基本概念:接口、类、对象,接口是规定一个类中必须要实现的接口,类实例化后得到类。假设我们现在要去买辆保时捷,那放在我们面前一个Car类(好可惜不是真的)。
class Car{}
我一眼就看中了penamera,那我想挑出几种配置来对比下价格,那首先我想要一台12缸的、500匹马力的,这样我拿到了一个发动机配置类。
class Engine
{
/*
* 发动机缸数
*/
protected $cylinder;
/**
* 马力
*/
protected $horsepower;
public function __construct($cylinder, $horsepower)
{
$this->cylinder = $cylinder;
$this->horsepower = $horsepower;
}
}
那我的车就成了如下配置
class Car{
protected $engine;
public function __construct()
{
$this->engine = new Engine(12,500);
}
}
这个时候要挑内饰了,我一眼看中了液晶仪表,运动座椅,现在我们得到一个内饰配置类,然后我想要一台红色的车,我又拿到一个颜色配置类。
class Trim
{
/**
* 仪表
*/
protected $instrument;
/**
* 座椅
*/
protected $seat;
public function __construct($instrument, $seat)
{
$this->instrument = $instrument;
$this->seat = $seat;
}
}
class Color
{
protected $color;
public function __construct($color)
{
$this->color = $color;
}
}
我的车又升级了,不过我有点烦,我还有一堆配置没选,而且当我改变之前的配置,我的车还得重造。
class Car
{
protected $engine;
protected $trim;
protected $color;
public function __construct()
{
$this->engine = new Engine(12, 500);
$this->trim = new Trim('lcd', 'sport');
$this->color = new Color('red');
.......(还有一堆配置)
}
}
转回正体,上面这种在我刚搬砖的时候觉得是一种非常优雅的写法,但是那上面例子来说,车有太多太多种配置,那我需要去new很多很多配置,那我们可以使用工厂模式来解决问题:
class ConfigurationFactory
{
public function makeCar($class, $configurations)
{
switch ($class) {
case 'Engine':
return new Engine($configurations[0], $configurations[1]);
case 'Trim':
return new Trim($configurations[0], $configurations[1]);
case 'Color':
return new Color($configurations[0]);
default:
return null;
}
}
}
class Car
{
protected $configuration;
public function __construct($configurations)
{
$factory = new ConfigurationFactory();
foreach ($configurations as $class => $configuration) {
$this->configuration[] = $factory->makeCar($class, $configuration);
}
}
}
使用工厂模式后,当我们增加配置的时候,不在需要去更改Car类,我们在ConfigurationFactory类中添加就可以了,但是Car类还是和外部资源存在耦合,那关键一步来了,我们可以使用到依赖注入来解耦。我可以为我想要的配置约束一个接口,这样我就可以赛选出我想要的几种配置。
interface Configuration
{
public function needConfiguration();
}
class Engine implements Configuration
{
public function needConfiguration()
{
// TODO: Implement needConfiguration() method.
}
}
class Trim implements Configuration
{
public function needConfiguration()
{
// TODO: Implement needConfiguration() method.
}
}
class Color implements Configuration
{
public function needConfiguration()
{
// TODO: Implement needConfiguration() method.
}
}
class Car
{
protected $configuration;
public function __construct(Configuration $configuration)
{
$this->configuration = $configuration;
}
}
那这个时候我们怎么得到我们的Car类呢,这时候惊喜来了:
$configuration = new Engine();
$car = new Car($configuration);
写到这我才发现我这个车的比喻到最后圆不上了,但是依赖注入的概念已经很明显了,来总结一下。
依赖注入是把服务容器中需要的实例在容器外部实例好注入到应用程序中。
控制反转是指服务容器中不再在容器内部控制实例的生成,而是在容器外部生成后再注入这些外部资源。
Ioc容器
那再来看看Ioc服务容器,应用程序中管理类依赖和执行依赖注入就是服务容器,我们可以用对象池模式来写个简单的服务容器,就像laravel中一样,你需要先把外部资源注册到容器中(bind),需要使用的时候就可以问服务容器索要(make)这个外部资源了。
class Container
{
protected $binds;
protected $instances;
public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) { //是否是匿名函数
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
当你需要咋容器中使用到外部资源,你可以向容器中bind注册你的资源,当你传入的是个实例好的对象,会储存在instances中,当你传入一个闭包,会存在binds中,make方法是像容器中拿到注入过的外部资源,首先会看instances中是否已经存在,否者回去binds中去找到闭包去实例。
举个栗子:
//容器
$container = new Container;
//注册资源
$container->bind('Car', function($container, $parameters) {
return new Car($container->make($parameters));
});
$container->bind('Engine', function() {
return new Engine();
});
$container->bind('Trim', function() {
return new Trim();
});
$color = new Color();
$container->bind('Color', $color);
获取资源
$car1 = container->make('Car', ['Engine']);
$car2 = container->make('Car', ['Trim']);
$car3 = container->make('Car', ['Color']);
这样我就获得了我的3种配置的Car。