专栏名称: 唤之
目录
相关文章推荐
码农翻身  ·  Bill Gates 和 Linus ... ·  9 小时前  
OSC开源社区  ·  敢自称Java版PyTorch,EasyAi ... ·  昨天  
OSC开源社区  ·  如何公正评价百度开源的贡献? ·  2 天前  
程序员的那些事  ·  感谢 ... ·  4 天前  
码农翻身  ·  DeepSeek彻底爆了! ·  昨天  
51好读  ›  专栏  ›  唤之

IOC 模式解耦与 Laravel 服务容器

唤之  · 掘金  · 程序员  · 2018-06-11 10:43

正文

简要介绍 IOC 模式进行代码解耦及其在 Laravel 服务容器中的应用。

本文代码: GitHub

前言

服务容器是 Laravel 框架实现模块化解耦的核心。模块化即是将系统拆成多个子模块,子模块间的耦合程度尽可能的低,代码中尽可能的避免直接调用。这样才能提高系统的代码重用性、可维护性、扩展性。

下边出行例子有火车、飞机两种出行方式,对应给出了 3 种耦合度越来越低的实现:高度耦合实现、工厂模式解耦、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 的一种实现方式,是指组件间的依赖通过外部参数(interface)形式直接注入。比如对上边的工厂模式进一步解耦:

<?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 反射和匿名函数,参考: Laravel 框架中常用的 PHP 语法

IOC 容器

高级依赖注入 = 简单依赖注入 + 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();

运行:







请到「今天看啥」查看全文