Dependency Injection 和 Service Locator



Dependency Injection 和 Service Locator

如果说学院派的 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 区别的关键是在哪使用容器:

如果在非工厂对象的外面使用容器,那么就属于 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 来获取也不错,这样也更简单直接。

此条目由老王发表在Technical分类目录,并贴了PHP标签。将固定链接加入收藏夹。
《DEPENDENCY INJECTION 和 SERVICE LOCATOR》上有6条评论
來自台灣的阿拉丁大神在2015-11-1211:50:07说道:
通篇沒有看到關鍵字 interface,你是哪裡來的膽子敢談 IoC?

Martin Fowler 的文章你到底看懂沒?自己歸納一些無腦的結論還在那邊自嗨…

回复 ↓
老王
在2015-11-1217:20:27说道:
我按我的理解写,你用你的智商看。

harveyz
在2015-12-1815:32:27说道:
看别人的文章,有存在异议的地方就用合理的方式指出来,说别人无脑,难道不觉得片面的、狭隘的评论更无脑吗?

温水青蛙
在2016-02-2417:30:49说道:
IoC面向接口编程,但不是必须是interface,接触过很多很教条的项目,到处都是接口,其实一个Service类正常不会多种实现,何苦非得定义一个interface出来,其实方法的参数和返回值就是一种约定了。关键是依赖注入,而不是调用方自己构架出来的。

test在2016-03-2620:18:49说道:
似乎没讲明白

回复 ↓
test
在2016-03-2622:03:39说道:
感觉需要多举几个例子…