理解依赖注入
依赖注入 和 依赖注入容器 是不同的:
- 依赖注入 (Dependency injection) 是编写更好代码的一种方法
- 容器 (Container) 是帮助注入依赖关系的工具
你不需要一个容器来执行依赖注入,但是一个容器可以帮助你。
PHP-DI就是这样做的:使依赖注入更加实用。
理论
经典的PHP代码
下面是不使用DI的代码大致工作的方式:
- 应用程序需要 Foo(例如一个控制器),所以:
- 应用程序创建 Foo
- 应用程序调用 Foo
- Foo 需要 Bar(例如一个服务),所以:
- Foo 创建 Bar
- Foo 调用 Bar
- Bar 需要 Bim(一个服务,一个仓库……),所以:
- Bar 创建 Bim
- Bar 做一些事情
使用依赖注入 (Dependency injection)
下面是使用DI的代码大致工作的方式:
- 应用程序需要 Foo ,它需要 Bar,它需要Bim,所以:
- 应用程序创建 Bim
- 应用程序创建 Bar 并给它 Bim
- 应用程序创建 Foo 并给它 Bar
- 应用程序调用 Foo
- Foo 调用 Bar
- Bar 做一些事情
- Foo 调用 Bar
这是控制反转的模式,被调用者和调用者之间的依赖性控制是相反的。
最主要的优点是:在调用链顶部的那个总是你。你可以控制所有依赖项,并完全控制您的应用程序的工作方式,你可以用另一个(例如你创建的一个)来替换依赖项。
例如,如果库X使用 Logger Y,而你想让它使用 Logger Z 呢?有了依赖注入,你就不需要更改库X的代码了。
使用容器 (Container)
那么,使用PHP-DI的代码是如何工作的:
- 应用程序需要 Foo,所以:
- 应用程序从 Container 获取 Foo,所以:
- Container 创建 Bim
- Container 创建 Bar 并给它 Bim
- Container 创建 Foo 并给它 Bar
- 应用程序调用 Foo
- Foo 调用 Bar
- Bar 做一些事情
- Foo 调用 Bar
简而言之,容器包含了创建和注入依赖的所有工作。
In short, the container takes away all the work of creating and injecting dependencies.
用一个例子来理解
这是一个真实的例子,比较了一个经典的实现(使用new
或单例)和使用依赖注入。
没有依赖注入
假设你有:
class GoogleMaps { public function getCoordinatesFromAddress($address) { // calls Google Maps webservice } } class OpenStreetMap { public function getCoordinatesFromAddress($address) { // calls OpenStreetMap webservice } }
经典的做法是: class StoreService { public function getStoreCoordinates($store) { $geolocationService = new GoogleMaps(); // or $geolocationService = GoogleMaps::getInstance() if you use singletons return $geolocationService->getCoordinatesFromAddress($store->getAddress()); } }
现在我们想使用OpenStreetMap
而不是GoogleMaps
,我们该怎么做?
我们必须更改StoreService
的代码,以及所有其他使用GoogleMaps
的类。
如果没有依赖注入,你的类与它们的依赖紧耦合。
使用依赖注入
StoreService
现在使用依赖注入:
class StoreService { private $geolocationService; public function __construct(GeolocationService $geolocationService) { $this->geolocationService = $geolocationService; } public function getStoreCoordinates($store) { return $this->geolocationService->getCoordinatesFromAddress($store->getAddress()); } }
服务是使用接口 (Interface) 定义的: interface GeolocationService { public function getCoordinatesFromAddress($address); } class GoogleMaps implements GeolocationService { ... class OpenStreetMap implements GeolocationService { ...
现在,StoreService的用户可以决定使用哪个实现。 它可以随时改变,不必重写StoreService。 StoreService不再与它的依赖紧耦合。 The StoreService is no longer tightly coupled to its dependency. 使用 PHP-DI 你可能会发现依赖注入会带来一个缺点:你现在必须处理注入依赖关系。 这就是容器(Container),特别是PHP-DI可以帮助你的地方。 而不是写:
$geolocationService = new GoogleMaps(); $storeService = new StoreService($geolocationService); 你可以写: $storeService = $container->get('StoreService');
并配置哪个GeolocationService
PHP-DI应该通过配置自动注入到StoreService
中:
$container->set('GeolocationService', \DI\create('GoogleMaps'));
如果您改变主意,现在只需要改变一行配置。
感兴趣吗? 继续阅读开始使用PHP-DI指南!
参考
p.s. 看到PHP-DI没有中文文档,第一次对着机翻瞎翻译,如有疏漏敬请指正。