简要介绍 IOC 模式进行代码解耦及其在 Laravel 服务容器中的应用。
本文代码:GitHub
前言
服务容器是 Laravel 框架实现模块化解耦的核心。模块化即是将系统拆成多个子模块,子模块间的耦合程度尽可能的低,代码中尽可能的避免直接调用。这样才能提高系统的代码重用性、可维护性、扩展性。
下边出行例子有火车、飞机两种出行方式,对应给出了 3 种耦合度越来越低的实现:高度耦合实现、工厂模式解耦、IOC 模式解耦。
高度耦合实现
代码实现
定义 TrafficTool 接口并用 Train、Plane 实现,最后在 Traveler 中实例化出行工具后说走就走。代码十分简洁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?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() { $this->travelTool = new Train(); }
public function travel() { $this->travelTool->go(); } }
$me = new Traveler(); $me->travel();
|
运行:
1 2
| $ php normal.php [Travel By]: train
|
优点
代码十分简洁:一个接口两个类最后直接调用。
缺点
在第 32 行,Traveler
与 Train
两个组件发生了耦合。以后想坐飞机出行,必须修改 __construct()
的内部实现:$this->travelTool = new Plane();
重用性和可维护性都很差:在实际的软件开发中,代码会根据业务需求的变化而不断修改。如果组件之间直接相互调用,那组件的代码就不能轻易修改,以免调用它的地方出现错误。
工厂模式解耦
工厂模式
分离代码中不变和变的部分,使得在不同条件下创建不同的对象。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| ...
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();
|
运行:
1 2
| $ php factory.php [Travel By]: train
|
优点
提取了代码中变化的部分:更换交通工具,坐飞机出行直接修改 $me = new Traveler('plane')
即可。适用于需求简单的情况。
缺点
依旧没有彻底解决依赖:现在 Traveler
与 TrafficToolFactory
发生了依赖。当需求增多后,工厂的 switch...case
等代码也不易维护。
IOC 模式解耦
IOC 是 Inversion Of Controll 的缩写,即控制反转。这里的“反转”可理解为将组件间依赖关系提到外部管理。
简单的依赖注入
依赖注入是 IOC 的一种实现方式,是指组件间的依赖通过外部参数(interface)形式直接注入。比如对上边的工厂模式进一步解耦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?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(TrafficTool $tool) { $this->trafficTool = $tool; }
public function travel() { $this->trafficTool->go(); } }
$train = new Train(); $me = new Traveler($train); $me->travel();
|
运行:
1 2
| $ php simple_ioc.php [Travel By]: train
|
高级依赖注入
简单注入的问题
如果三个人分别自驾游、坐飞机、高铁出去玩,那你的代码可能是这样的:
1 2 3 4 5 6 7 8 9 10 11
| $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 容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <?php
... 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, $params = []) { if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; }
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();
|
运行:
1 2
| $ php advanced_ioc.php [Travel By]: train
|
简化并解耦后的代码
那三个人再出去玩,代码将简化为:
1 2 3 4 5 6 7
| $a = $container->make('traveler', ['car']); $b = $container->make('traveler', ['train']); $c = $container->make('traveler', ['plane']);
$a->travel(); $b->travel(); $c->travel();
|
更多参考:神奇的服务容器
Laravel 的服务容器
Laravel 自己的服务容器是一个更加高级的 IOC 容器,它的简化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| <?php
...
class Container { public $binds = [];
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; }
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();
$container->bind('TrafficTool', 'Train'); $container->bind('traveller', 'Traveller');
$me = $container->make('traveller'); $me->travel();
|
运行:
1 2
| $ php laravel_ioc.php [Travel By]: train
|
Train 类要能被实例化,需要先注册到容器,这就涉及到 Laravel 中服务提供者(Service Provider)的概念了。至于服务提供者是怎么注册类、注册之后如何实例化、实例化后如何调用的… 下节详细分析。
总结
本文用一个旅游出行的 demo,引出了高度耦合的直接实现、工厂模式解耦和 IOC 模式解耦共计三种实现方式,越往后代码量越多还有些绕,但类(模块)之间的耦合度越来越低,最后实现了简化版的 Laravel 服务容器。
Laravel 的优美得益于开发的组件式解耦,这与服务容器和服务提供者的理念是离不开的,下篇将用 Laravel 框架 laravel/framework/src/Illuminate/Container.php
中 Container
类来梳理 Laravel 服务容器的工作流程。