Разделение шаблонов IOC и сервисный контейнер Laravel

PHP Laravel

Кратко представим шаблон 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.