原文:blog,转载注明来源便可。
本文代码:GitHubphp
服务容器是 Laravel 框架实现模块化解耦的核心。模块化便是将系统拆成多个子模块,子模块间的耦合程度尽量的低,代码中尽量的避免直接调用。这样才能提升系统的代码重用性、可维护性、扩展性。laravel
下边出行例子有火车、飞机两种出行方式,对应给出了 3 种耦合度愈来愈低的实现:高度耦合实现、工厂模式解耦、IOC 模式解耦。git
定义 TrafficTool 接口并用 Train、Plane 实现,最后在 Traveler 中实例化出行工具后说走就走。代码十分简洁:github
<?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();
运行:shell
$ php normal.php [Travel By]: train
代码十分简洁:一个接口两个类最后直接调用。数组
在第 32 行,Traveler
与 Train
两个组件发生了耦合。之后想坐飞机出行,必须修改 __construct()
的内部实现:$this->travelTool = new Plane();
框架
重用性和可维护性都不好:在实际的软件开发中,代码会根据业务需求的变化而不断修改。若是组件之间直接相互调用,那组件的代码就不能轻易修改,以避免调用它的地方出现错误。ide
分离代码中不变和变的部分,使得在不一样条件下建立不一样的对象。模块化
... 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 是 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 容器
<?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 本身的服务容器是一个更加高级的 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 中服务提供者(Service Provider)的概念了。至于服务提供者是怎么注册类、注册以后如何实例化、实例化后如何调用的... 下节详细分析。
本文用一个旅游出行的 demo,引出了高度耦合的直接实现、工厂模式解耦和 IOC 模式解耦共计三种实现方式,越日后代码量越多还有些绕,但类(模块)之间的耦合度愈来愈低,最后实现了简化版的 Laravel 服务容器。
Laravel 的优美得益于开发的组件式解耦,这与服务容器和服务提供者的理念是离不开的,下篇将用 Laravel 框架 laravel/framework/src/Illuminate/Container.php
中 Container
类来梳理 Laravel 服务容器的工做流程。