浅谈依赖注入与控制反转

思考并回答以下问题:

  • 工厂模式的应用场景就是解决改名问题如何理解?
    *

来源:浅谈依赖注入与控制反转

前言:设计模式其实是一个很空洞的东西,设计模式有几十种,有些人觉得工厂模式和单例模式已经足够解决大部分问题。而有些人觉得任何设计模式都会让开发变得更“复杂”,更“低效”。所以千万不要太过追求他的实际意义和作用,否则你已经坠入云雾。但是不管怎么样,实际工作中还是要对它们有所了解,下面从php的角度来讲一下依赖注入、控制反转、反射等概念。

首先设定场景,假如一个类需要数据库连接,最简单的做法可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
class example 
{
private $_db;
function __construct()
{
include "./Lib/Db.php";
$this->_db = new Db("localhost","root","123456","test");
}
function getList()
{
$this->_db->query("......");
}
}

但事实上稍微有点经验的同学都不会这样写,因为一旦越来越多的类用到db,而db一旦发生变化(名字或路径改变),那么岂不是要每个文件都修改一遍?这就是程序设计中的耦合问题。所有的类过度依赖“./Lib/Db.php”这个文件。OK,为了解决这个问题,工厂模式出现了,我们新建一个Factory工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Factory 
{
public static function getDb()
{
include "./Lib/Db.php";
return new Db("localhost","root","123456","test");
}
}
class example
{
private $_db;
function __construct()
{
$this->_db = Factory::getDb();
}
function getList()
{
$this->_db->query("......");
}
}

如果我们用到db模块那么直接Factory::getDb()从工厂中取出来就是了,看似解决了问题。但事实是这样吗?不,这样只不过是把程序与db模块的耦合转移到了Factory,一旦后期业务发生变动,Factory发生变动(名字改了),依旧要对每个文件改动。那如何解决呢?

我们可以不从example类内部获取db组件,我们从外部把db组件注入进example类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class example 
{
private $_db;
function getList()
{
$this->_db->query("......");//执行查询
}
//从外部注入db连接
function setDb($connection)
{
$this->_db = $connection;
}
}
$example = new example();
$example->setDb(Factory::getDb());//注入db连接
$example->getList();

这样一来example就不用关心db组件怎么来的了,只用暴露一个注入方法即可。这就是DI/依赖注入(Dependency Injection),不在内部处理依赖关系,而是把依赖作为参数传递进去,以降低程序的耦合性。

然后我们的项目继续进行,用到了文件处理类,图像处理类,我们可能会这样写:

1
2
3
$example->setDb(Factory::getDb());//注入db连接
$example->setFile(Factory::getFile());//注入文件处理类
$example->setImage(Factory::getImage());//注入Image处理类

但是这样似乎也不行啊,每次都要写这么多代码,于是我们又写了一个工厂方法:

1
2
3
4
5
6
7
8
9
10
11
class Factory 
{
public static function getExample()
{
$example = new example();
$example->setDb(Factory::getDb());//注入db连接
$example->setFile(Factory::getFile());//注入文件处理类
$example->setImage(Factory::getImage());//注入Image处理类
return $expample;
}
}

example也不直接new了。我们用Factory::getExample()中获取。但是,这是不是又有点熟悉的感觉?和上面第一次用工厂类的时候一样依赖于工厂。于是又有了容器的概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class example 
{
private $_di;
function __construct(Di &$di)
{
$this->_di = $di;
}
//通过di容器获取db实例
function getList()
{
$this->_di->get('db')->query("......");
}
}
$di = new Di();
$di->set("db",function(){
return new Db("localhost","root","root","test");
});
$example = new example($di);
$example->getList();

Di就是一个存放各种可扩展的组件的容器,需要注入的时候调用di->set()方法注入组件即可。程序中即可通过di->get()获取组件。这样被调用的组件(db)并不是由调用者(example)创建,而是由Di容器创建,调用者失去控制权,而容器得到控制权,发生了控制权转移,所以叫 做控制反转(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
55
56
<?php
/**
* example
*/
class example
{
//通过di容器获取db实例
function getList()
{
$this->_di->get('db')->query("......");
}
}
//di容器
class Di
{
public $_container;
public function get($cls)
{
return $this->_container[$cls];
}
public function set($cls,$_instance)
{
$this->_container[$cls]=$_instance;
}
}
//db组件
class db
{
private static $_instance;//保存单例
//单例方法
public static function getInstance()
{
if(!(self::$_instance instanceof self))
{
self::$_instance = new self;
}
return self::$_instance;
}
//查询方法
public function query($sql)
{
echo $sql;
}
}

$di = new Di();//实例化容器
$di->set('db',db::getInstance()); //注入db实例

$reflector = new ReflectionClass('example'); //反射example,通过反射可以获得该类的所有信息
$reflector->getDefaultProperties(); //example属性
$reflector->getDocComment(); //注释

$instance =$reflector->newInstanceArgs(); //相当于实例化反射的example类
$instance->_di=$di; //注入di容器

$reflector->getmethod('getList')->invoke($instance);//调用example类方法

通过反射我们可以得到该类的全部信息,包括方法、方法名、属性甚至注释等等。通过反射我们可以方便的控制程序中使用的类,对他们进行扩展、修正、以及监听。通常反射在插件开发和框架开发中大量应用。在框架开发中也会把反射与依赖注入、控制反转搭配使用,让程序有强大的可控性和扩展性。

依赖注入的本质就是自动实例化类的一个过程,合理的使用依赖注入恰恰能提高性能。

0%