如果说学院派的 Java 程序员骨子里都浸淫着学究范儿的话,那么游击队出身的 PHP 程序员则从头到脚洋溢着乡土气息。通常他们不太在意理论,一切以实现为先,虽然这样的做法在项目早期能获得不错的收益,但是随着项目的推进,复杂度的提升,缺乏理论基础的弊端终将显现。好在 PHP 社区没有裹足不前,比如说十几年前 Java 社区中流行的 IoC 概念,最近一两年终于被 PHP 社区所接纳。
说起 IoC,其实是 Inversion of Control 的缩写,翻译成中文叫控制反转,不得不说这个名字起得让人丈二和尚摸不着头脑,实际上简而言之它的意思是说对象之间难免会有各种各样的依赖关系,如果我们的代码直接依赖于具体的实现,那么就是一种强耦合,从而降低了系统的灵活性,为了解耦,我们的代码应该依赖接口,至于具体的实现,则通过第三方注入进去,这里的第三方通常就是我们常说的容器。因为在这个过程中,具体实现的控制权从我们的代码转移到了容器,所以称之为控制反转。
如果你看过 Martin Fowler 关于 IoC 的介绍,那么你就会知道 IoC 是一个宽泛的概念,具体点儿说有两种不同的实现方式,分别是:Dependency Injection 和 Service Locator。现在很多 PHP 框架都实现了容器,比如 Phalcon (1),Yii (1)(2),Laravel (1)(2) 等。
至于 Dependency Injection 和 Service Locator 的区别,与其说一套云山雾绕的概念,不能给出几个鲜活的例子来得自然,为了偷懒,我直接套用 TheKeyboard 的文章:
如果没有容器,那么 Dependency Injection 看起来就像:
<?php
class Foo
{
protected $_bar;
protected $_baz;
public function __construct(Bar $bar, Baz $baz) {
$this->_bar = $bar;
$this->_baz = $baz;
}
}
// In our test, using PHPUnit's built-in mock support
$bar = $this->getMock('Bar');
$baz = $this->getMock('Baz');
$testFoo = new Foo($bar, $baz);
?>
如果有容器,那么 Dependency Injection 看起来就像:
<?php
// In our test, using PHPUnit's built-in mock support
$container = $this->getMock('Container');
$container['bar'] = $this->getMock('Bar');
$container['baz'] = $this->getMock('Baz');
$testFoo = new Foo($container['bar'], $container['baz']);
?>
通过引入容器,我们可以把所有的依赖都集中管理,这样有很多好处,比如说我们可以很方便的替换某种依赖的实现方式,从而提升系统的灵活性。
看看下面这个实现怎么样?是不是 Dependency Injection?
<?php
class Foo
{
protected $_bar;
protected $_baz;
public function __construct(Container $container) {
$this->_bar = $container['bar'];
$this->_baz = $container['baz'];
}
}
// In our test, using PHPUnit's built-in mock support
$container = $this->getMock('Container');
$container['bar'] = $this->getMock('Bar');
$container['baz'] = $this->getMock('Bar');
$testFoo = new Foo($container);
?>
虽然从表面上看它也使用了容器,并不依赖具体的实现,但你如果仔细看就会发现,它依赖了容器本身,实际上这不是 Dependency Injection,而是 Service Locator。
于是乎判断 Dependency Injection 和 Service Locator 区别的关键是在哪使用容器:
之所以排除工厂对象是因为它是一种特殊的对象,它关注的是创建对象,而不是操作对象,具体的解释可以参考 Paul M. Jones 在一系列文章中的解释。
说到这里,我想顺带提一下 Laravel 的 Facade 概念,它是一种 Service Locator 的语法糖,原理可以参考:How Laravel Facades Work and How to Use Them Elsewhere。很多人建议 Stop Using Facades,Laravel 作者也给出了回应。
BTW:Laravel 中的 Facade 实际有误导之嫌,详见:Let’s Talk About Facades。
说了这么多,我们应该如何取舍 Dependency Injection 和 Service Locator 呢?实际上它们各有各的优缺点,比如说 Dependency Injection 解耦更彻底,而 Service Locator 使用更直接。如果是一些可复用性强的对象,如 Model,那么它的依赖最好使用 Dependency Injection 来获取;如果是一些可复用性弱的对象,如 Controller,那么它的依赖并不一定要强解耦,使用 Service Locator 来获取也不错,这样也更简单直接。