当下所流行的php框架几乎都应用了依赖注入这项历久弥新的设计模式, 比如Symfony,Laravel,Yii等等。作为phper如果不懂这个模式未免有点落伍, 那我们今天就来研究学习依赖注入。

何为依赖注入

我们假设这样的一个应用场合, 在项目中需要应用到搜索服务, 当下这个搜索服务是基于Sphinx实现的, 当项目发展一段时间之后, 我们发现其实Elasticsearch更适合我们的项目需要, 因此我们需要把原先项目中应用到Sphinx的部分转换变为Elasticsearch, 而Sphinx与Elasticsearch内部实现各不相同, 项目中原先调用的地方不得不一片大改. 很显然这种强依赖于具体实现的模式是不利于后期项目的维护与扩展的, 那有没有一种模式能应对这种情况呢?
依赖注入(Denpdency Inject)按照字面的含义就是把依赖注入到某个事物当中, 是控制反转(Inversion of Control)的一种实现方式。当某个项目需要应用到某个服务的时候, 并不是直接在项目具体实现的当下去调用这个服务, 而是在初始化的过程中把服务注入到项目当中, 而服务也应接受接口的约束, 方便服务的更替。

依赖注入具体实现

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
<?php

class Container
{
protected $binds;

protected $instances;

public function set($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}

public function get($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

array_unshift($parameters, $this);

return call_user_func_array($this->binds[$abstract], $parameters);
}
}

Interface SearchInterFace
{
public function search($keywords);
}

Class Sphinx implements SearchInterFace
{
public function search($keywords){
echo "正在用".get_class()."搜索{$keywords}\n";
}
}

Class Elasticsearch implements SearchInterFace
{
public function search($keywords){
echo "正在用".get_class()."搜索{$keywords}\n";
}
}

$container = new Container();
$container->set('search', function (){
return new Sphinx();
});
$container->get('search')->search('nike');

这时候我们需要把搜索服务从Sphinx迁移Elasticsearch只要在注入处修改就可以了。

1
2
3
4
5
$container = new Container();
$container->set('search', function (){
return new ElasticSearch();
});
$container->get('search')->search('nike');

其实这里代码实现的还包括容器的概念, 在需要使用时候达到延迟加载的功能, 这里不做讨论。