依赖注入(DI)

假设实现一个汽车类Car,它依赖一个引擎类Engine:

1
2
3
4
5
6
7
8
9
class Car
{

public function go()
{

$engine = new Engine();
$engine->start();
echo "go";
}
}

这时如果汽车的Engine变为TurboEngine的时候,就需要修改汽车类Car的实现。因此为了解耦,我们需要把引擎类注入到汽车类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Car
{

protected $engine;
public function __construct($engine)
{

$this->_engine = $engine;
}
public function go()
{

$this->_engine->start();
echo "go";
}
}

$engine = new Engine();
$car = new Car($engine);

这样就实现了Car类和Engine类的解耦,这种方法就是依赖注入(Dependency Injection)
依赖注入一般有三种方法:

  • 构造函数
  • setter
  • 接口

这种使用类似于依赖注入的方式来管理依赖,实现解耦的设计模式就是IoC模式(控制反转模式,Inversion of Control)

p.s. 关于IoC,这篇文章讲的比较通俗。

IoC容器

假设Car类变得很复杂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Car
{

protected $engine;
protected $tire;
public function __construct(Engine $engine,Tire $tire)
{

$this->_engine = $engine;
$this->_tire = $tire;
}
public function go()
{

$this->_tire->warm();
$this->_engine->start();
echo "go";
}
}

$tire = new Tire();
$fuel = new Fuel();
$engine = new Engine($fuel);
$car = new Car($engine, $tire);

这样带来的一个问题是,当我需要实例化Car的时候,需要首先去实例化很多它依赖的类。让这么懒的我去做这种事情是不现实的。这样就引出了IoC容器这个东西。
IoC容器就是用来解析类的依赖,绑定接口和实现的一个东西。说起来比较抽象,还是举个栗子:
Lavarel中的APP就是一个IoC容器,它是Application类的一个实例,Application类继承了Container。下面是一个Case(来自这里):

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
class Car {
    protected $tire;
    protected $engine;
    public function __construct(Tire $tire, Engine $engine) {
        $this->tire = $tire;
        $this->engine = $engine;    
    }
}
 
class Tire {}
 
class Engine {}
 
App::bind('Car', function()
{

    return new Car(new Tire, new Engine);
});
 
 
Route::get('/', function()
{

    dd(App::make('Car'));  //  dd是一个类似var_dump的方法
});

//输出:
object(Car)[145]
 protected 'tire' => 
   object(Tire)[143]
 protected 'engine' => 
   object(Engine)[150]

bind方法是把Car绑定到容器中,当调用make时,就可以得到一个Car的实例。

更NB的地方在于:

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
class Car {
    protected $tire;
    protected $engine;
    public function __construct(Tire $tire, Engine $engine) {
        $this->tire = $tire;
        $this->engine = $engine;    
    }
}
 
class Tire {}
 
class Engine {}
 
Route::get('/', function()
{

    dd(App::make('Car'));
});
 
//注意没有bind了
//输出:
object(Car)[152]
 protected 'tire' => 
   object(Tire)[153]
 protected 'engine' => 
   object(Engine)[154]

这里仍然能够正常运行,因为Laravel的容器做了这些事情:

  1. 检查用户是否绑定了Car?(如果已经绑定,就直接使用);
  2. 如果没有绑定, 就查询Car的依赖关系;
  3. 解析所有Car需要的依赖关系(Tire 和 Engine);
  4. 创建Car的新实例,包含Car所依赖的所有关系;

这里不管类多复杂,依赖层次多么深,容器都会帮你搞定依赖关系。

实现

这么NB的东西是怎么实现的呢?
应该是靠PHP的反射来做的,具体等读读源码看看。

留言