Кратко представим шаблон IOC для разделения кода и его применение в сервис-контейнере Laravel.
Код для этой статьи:GitHub
предисловие
Сервисный контейнер — это ядро фреймворка Laravel для достижения модульной развязки. Модульность заключается в разделении системы на несколько подмодулей, степень связи между подмодулями как можно ниже, а прямые вызовы в коде максимально избегаются. Только таким образом можно улучшить повторное использование кода, ремонтопригодность и расширяемость системы.
Следующие примеры движения включают два режима движения: поезд и самолет и, соответственно, дают три реализации с более низкой и более низкой степенью связи: реализация с высокой степенью связи, развязка заводского режима и развязка режима IOC.
Высокосвязанная реализация
Код
Определите интерфейс TrafficTool и реализуйте его с помощью Train и Plane, и, наконец, создайте экземпляр инструмента для путешествий в Traveler и вперед. Код очень лаконичен:
<?php
// 定义交通工具接口
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go() {
echo '[Travel By]: train', PHP_EOL;
}
}
class Plane implements TrafficTool
{
public function go() {
echo '[Travel By]: plane', PHP_EOL;
}
}
// 旅游者类,使用火车出行
class Traveler
{
protected $trafficTool;
public function __construct() {
// 直接 new 对象,在 Traveler 与 Train 两个类之间产生了依赖
// 如果程序内部要修改出行方式,必须修改 Traveler 的 __construct()
// 代码高度耦合,可维护性低
$this->travelTool = new Train();
}
public function travel() {
$this->travelTool->go();
}
}
$me = new Traveler();
$me->travel();
бегать:
$ php normal.php
[Travel By]: train
преимущество
Код очень лаконичен: один интерфейс и два класса, наконец, вызываются напрямую.
недостаток
В строке 32,Traveler
а такжеTrain
Два компонента соединены. Если вы хотите путешествовать на самолете в будущем, вы должны изменить его__construct()
Внутренняя реализация:$this->travelTool = new Plane();
Повторное использование и ремонтопригодность плохие: при реальной разработке программного обеспечения код постоянно модифицируется по мере изменения потребностей бизнеса. Если компоненты вызывают друг друга напрямую, код компонента нельзя легко модифицировать, чтобы избежать ошибок в том месте, где он вызывается.
Развязка заводского шаблона
заводской узор
Разделение постоянной и переменной частей кода, чтобы разные объекты создавались в разных условиях.
Код
...
class TrafficToolFactory
{
public function create($name) {
switch ($name) {
case 'train':
return new Train();
case 'plane':
return new Plane();
default:
exit('[No Traffic Tool] :' . $name);
}
}
}
// 旅游者类,使用火车出行
class Traveler
{
protected $trafficTool;
public function __construct($toolName) {
// 使用工厂类实例化需要的交通工具
$factory = new TrafficToolFactory();
$this->travelTool = $factory->create($toolName);
}
public function travel() {
$this->travelTool->go();
}
}
// 传入指定的方式
$me = new Traveler('train');
$me->travel();
бегать:
$ php factory.php
[Travel By]: train
преимущество
Извлечена измененная часть кода: изменить средство передвижения и напрямую изменить его при путешествии на самолете.$me = new Traveler('plane')
Вот и все. Подходит для простых нужд.
недостаток
Еще не до конца решенные зависимости: теперьTraveler
а такжеTrafficToolFactory
Возникает зависимость. Когда спрос увеличивается, фабрикаswitch...case
Код также не прост в обслуживании.
Развязка шаблона IOC
IOC — это сокращение от Inversion Of Controll, то есть инверсия управления. Под «инверсией» здесь можно понимать внешнее управление зависимостями между компонентами.
Простая инъекция зависимостей
Внедрение зависимостей — это реализация IOC, что означает, что зависимости между компонентами внедряются напрямую в виде внешних параметров (интерфейса). Например, дальнейшее разделение приведенного выше фабричного шаблона:
<?php
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go() {
echo '[Travel By]: train', PHP_EOL;
}
}
class Plane implements TrafficTool
{
public function go() {
echo '[Travel By]: plane', PHP_EOL;
}
}
class Traveler
{
protected $trafficTool;
// 参数 $tool 就是控制反转要反转部分,将依赖的对象直接传入即可
// 以后再有 Car, GetWay ... 等新增工具也是实例化后传参直接调用
public function __construct(TrafficTool $tool) {
$this->trafficTool = $tool;
}
public function travel() {
$this->trafficTool->go();
}
}
$train = new Train();
$me = new Traveler($train); // 将依赖直接以参数的形式注入
$me->travel();
бегать:
$ php simple_ioc.php
[Travel By]: train
Расширенная инъекция зависимостей
Простая проблема с инъекцией
Если три человека путешествуют на машине, садятся в самолет и отправляются играть на скоростной железной дороге, то ваш код может быть таким:
$train = new Train();
$plane = new Plane();
$car = new Car();
$a = new Traveler($car);
$b = new Traveler($plane);
$c = new Traveler($train);
$a->travel();
$b->travel();
$c->travel();
Похоже на два слова: синий и тонкий. Вышеупомянутая простая инъекция зависимостей была сильно разделена по сравнению с фабричным шаблоном.Что касается концепции сервисных контейнеров в Laravel, разделение может продолжаться. Будут использоваться рефлексия PHP и анонимные функции, см.:Синтаксис PHP, обычно используемый в среде Laravel
МОК контейнер
Расширенная инъекция зависимостей = простая инъекция зависимостей + контейнер IOC
<?php
# advanced_ioc.php
...
class Container
{
protected $binds = [];
protected $instances = [];
/**
* 绑定:将回调函数绑定到字符指令上
*
* @param $abstract 字符指令,如 'train'
* @param $concrete 用于实例化组件的回调函数,如 function() { return new Train(); }
*/
public function bind($abstract, $concrete) {
if ($concrete instanceof Closure) {
// 向容器中添加可以执行的回调函数
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
/**
* 生产:执行回调函数
*
* @param $abstract 字符指令
* @param array $params 回调函数所需参数
* @return mixed 回调函数的返回值
*/
public function make($abstract, $params = []) {
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 此时 $this 是有 2 个元素的数组
// Array (
// [0] => Container Object (
// [binds] => Array ( ... )
// [instances] => Array()
// )
// [1] => "train"
// )
array_unshift($params, $this);
// 将参数传递给回调函数
return call_user_func_array($this->binds[$abstract], $params);
}
}
$container = new Container();
$container->bind('traveler', function ($container, $trafficTool) {
return new Traveler($container->make($trafficTool));
});
$container->bind('train', function ($container) {
return new Train();
});
$container->bind('plane', function ($container) {
return new Plane();
});
$me = $container->make('traveler', ['train']);
$me->travel();
бегать:
$ php advanced_ioc.php
[Travel By]: train
Упрощенный и несвязанный код
Эти три человека снова тусуются, и код упростится до:
$a = $container->make('traveler', ['car']);
$b = $container->make('traveler', ['train']);
$c = $container->make('traveler', ['plane']);
$a->travel();
$b->travel();
$c->travel();
Больше ссылок:Волшебный сервисный контейнер
Сервисный контейнер Laravel
Собственный сервисный контейнер Laravel является более продвинутым контейнером IOC, и его упрощенный код выглядит следующим образом:
<?php
# laravel_ioc.php
...
class Container
{
// 绑定回调函数
public $binds = [];
// 绑定接口 $abstract 与回调函数
public function bind($abstract, $concrete = null, $shared = false) {
if (!$concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->binds[$abstract] = compact('concrete', 'shared');
}
// 获取回调函数
public function getClosure($abstract, $concrete) {
return function ($container) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
return $container->$method($concrete);
};
}
protected function getConcrete($abstract) {
if (!isset($this->binds[$abstract])) {
return $abstract;
}
return $this->binds[$abstract]['concrete'];
}
// 生成实例对象
public function make($abstract) {
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($abstract, $concrete)) {
$obj = $this->build($concrete);
} else {
$obj = $this->make($concrete);
}
return $obj;
}
// 判断是否要用反射来实例化
protected function isBuildable($abstract, $concrete) {
return $concrete == $abstract || $concrete instanceof Closure;
}
// 通过反射来实例化 $concrete 的对象
public function build($concrete) {
if ($concrete instanceof Closure) {
return $concrete($this);
}
$reflector = new ReflectionClass($concrete);
if (!$reflector->isInstantiable()) {
echo "[can't instantiable]: " . $concrete;
}
$constructor = $reflector->getConstructor();
// 使用默认的构造函数
if (is_null($constructor)) {
return new $concrete;
}
$refParams = $constructor->getParameters();
$instances = $this->getDependencies($refParams);
return $reflector->newInstanceArgs($instances);
}
// 获取实例化对象时所需的参数
public function getDependencies($refParams) {
$deps = [];
foreach ($refParams as $refParam) {
$dep = $refParam->getClass();
if (is_null($dep)) {
$deps[] = null;
} else {
$deps[] = $this->resolveClass($refParam);
}
}
return (array)$deps;
}
// 获取参数的类型类名字
public function resolveClass(ReflectionParameter $refParam) {
return $this->make($refParam->getClass()->name);
}
}
$container = new Container();
// 将 traveller 对接到 Train
$container->bind('TrafficTool', 'Train');
$container->bind('traveller', 'Traveller');
// 创建 traveller 实例
$me = $container->make('traveller');
$me->travel();
бегать:
$ php laravel_ioc.php
[Travel By]: train
Прежде чем можно будет создать экземпляр класса Train, его необходимо сначала зарегистрировать в контейнере, что включает в себя концепцию поставщика услуг в Laravel. Что касается того, как поставщик услуг регистрирует класс, как создать экземпляр после регистрации и как вызвать после создания экземпляра... В следующем разделе будет подробно разобрано.
Суммировать
В этой статье используется демонстрация путешествия, чтобы представить три метода реализации: сильно связанная прямая реализация, развязка режима фабрики и развязка режима IOC Чем больше объем кода, тем он сложнее, но различия между классами (модулями) Связь становится все ниже и ниже, и, наконец, реализована упрощенная версия сервис-контейнера Laravel.
Преимущество Laravel заключается в разделении разработки на основе компонентов, которое неотделимо от концепции сервис-контейнеров и сервис-провайдеров.В следующей части будет использоваться фреймворк Laravel.laravel/framework/src/Illuminate/Container.php
серединаContainer
Класс для сортировки рабочего процесса сервисного контейнера Laravel.