一. 依赖注入和控制反转是什么?
名词解释:
IoC
- 控制反转 Inversion of Control
- 依赖关系的转移
- 依赖抽象而非实践
DI
- 依赖注入 Dependency Injection
- 不必自己在代码中维护对象的依赖
- 容器自动根据配置,将依赖注入指定对象
Container
- 管理对象的生成、资源取得、销毁等生命周期
- 建立对象与对象之间的依赖关系
- 启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系
------------以上引用自网络
二. IoC代码演示
高层应用程序需要调用底层模块接口,导致高层应用程序对低层模块产生依赖。
<?
// 高层类
class Order
{
private $track;
public function __construct()
{
$this->track = new sf(); //顺丰快递
}
public function query()
{
return $this->track->select();
}
}
// 底层类,快递接口
class sf
{
public function select
{
return "快递已送达";
}
}
$order = new Order();
$order->query();
假设我们有一个订单类,需要跟踪订单的物流信息,物流目前是和顺丰快递进行合作。但后因种种原因,需要迁移快递物流平台到圆通上去,那现在因为产生了物流接口依赖,程序无法重用,并且必须要修改或者新增圆通快递接口。这就是由于低层模块变化导致高层也跟着变化,不好的设计。
而正如上面的IoC解释
- 控制反转 Inversion of Control
- 依赖关系的转移
- 依赖抽象而非实践
程序不应该依赖于具体的实现,而是要依赖抽象的接口。如下:
<?php
// 物流平台接口
interface trackBox
{
public function select();
}
// 高层类
class Order
{
private $track;
public function setTrack($track)
{
$this->track = $track;
}
public function query()
{
return $this->track->select();
}
}
// 底层 顺丰
class sf implements trackBox
{
public function select()
{
return "顺丰快递已送达";
}
}
// 底层 圆通
class yt implements trackBox
{
public function select()
{
return "圆通快递已送达";
}
}
$order = new Order();
$order->setTrack(new sf());
$order->query(): //顺丰快递已送达
$order->setTrack(new yt());
$order->query(): //圆通快递已送达
这样就将原来的物流查询模块对程序的控制权转移到trackBox接口上,让order类和各种物流接口类都依赖于trackBox,这样就实现了控制反转。面对变化,高层不用修改一行代码,不再依赖低层,而是依赖注入,这也就引出了(依赖注入)DI。<br/>
依赖注入的方式有很多,但是主要思想就是如上所示,我们将一个依赖通过注入的方式进行使用。而不是直接将依赖写入被依赖中进行具体的实现。
三. 依赖注入容器 Dependency Injection Container
那我们接下来继续思考,如果此后业务扩张,合作的物流平台越来越丰富,那么高层程序依赖的物流接口就会越来越多,我们就需要创建的依赖接口实例也会越来越多,这样就很不利于后期的维护
//底层物流接口
class sf implements trackBox{ ... } //顺丰
class yt implements trackBox{ ... } //圆通
class zt implements trackBox{ ... } //中通
class yd implements trackBox{ ... } //韵达
class ems implements trackBox{ ... } //EMS
//依赖注入
$order = new Order();
$order->setTrack(new sf());
$order->select(): //顺丰快递已送达
$order->setTrack(new yt());
$order->select(): //圆通快递已送达
$order->setTrack(new zt());
$order->select(): //中通快递已送达
$order->setTrack(new yd());
$order->select(): //韵达快递已送达
$order->setTrack(new ems());
$order->select(): //EMS快递已送达
这个时候,我们有一种比较优雅的设计方案就是采用"工厂模式",
工厂模式,个人理解就是一个类所依赖的外部实例,可以被一个或多个"工厂"创建的这样一种开发模式
我们不手动进行创建依赖实例,而是通过工厂去创建依赖实例,我们需要做的只是通过工厂拿到我们需要用到的依赖实例
<?
// 工厂类
class TrackFactory
{
public function makeTrack($trackName)
{
switch ($trackName) {
case 'sf': return new sf();
case 'yt': return new yt();
case 'zt': return new zt();
case 'yd': return new yd();
case 'ems': return new ems();
}
// return new $trackName();
}
}
// 高层类
class Order
{
private $track;
public function __construct(array $tracks)
{
// 初始化工厂类
$trackFactory = new TrackFactory();
// 通过工厂类提供的方法制造所需要的物流平台
foreach ($tracks as $trackName) {
$this->$track[] = $trackFactory->makeTrack($trackName);
}
}
public function query($trackName)
{
return $this->track[$trackName]->select();
}
}
$Order = new Order(['sf','yd','ems','zt',]);
$Order->query('sf');
$Order->query('yd');
$Order->query('ems');
尽管工厂模式让我们的代码变得优雅了一点,但是我们又回到了开头的错误中来,我们现在对工厂类进行了依赖,并且在物流接口愈加丰富后,工厂类也会慢慢变的冗长而不利于维护,那我们要怎么才能设计出一套即优雅又松耦合的应用程序呢?这个时候就需要有容器这个概念
Container
- 管理对象的生成、资源取得、销毁等生命周期
- 建立对象与对象之间的依赖关系
- 启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系
<?
// 容器类
class Container
{
protected $binds;
public function bind($service, $instance)
{
if ($concrete instanceof Closure) {
$this->binds[$service] = $instance;
}
}
public function make($service, $parameters)
{
return call_user_func_array([$this->binds[$service],], $parameters);
}
}
// 高层类
class Order
{
private $track;
public function __construct($track)
{
$this->track = $track;
}
public function query()
{
return $this->track->select();
}
}
$container = new Container();
$container->bind('sf', function($container) {
return new sf();
});
$container->bind('Order', function($container) {
return new Order();
});
$Order = $container->make('Order','sf');