首先依賴注入和控制反轉說的是同一個東西,是一種設計模式,這種設計模式用來減少程序間的耦合,鄙人學習了一下,看TP官網還沒有相關的文章,就寫下這篇拙作介紹一下這種設計模式,希望能為TP社區貢獻一些力量。
首先先別追究這個設計模式的定義,否則你一定會被說的雲里霧里,筆者就是深受其害,百度了N多文章,都是從理論角度來描述,充斥着大量的生澀詞匯,要么就是java代碼描述的,也生澀。
不管怎么樣,總算弄清楚一些了,下面就以php的角度來描述一下依賴注入這個概念。
先假設我們這里有一個類,類里面需要用到數據庫連接,按照最最原始的辦法,我們可能是這樣寫這個類的:
|
1
2
3
4
5
6
7
8
9
10
11
|
class
example {
private
$_db
;
function
__construct(){
include
"./Lib/Db.php"
;
$this
->_db =
new
Db(
"localhost"
,
"root"
,
"123456"
,
"test"
);
}
function
getList(){
$this
->_db->query(
"......"
);
//這里具體sql語句就省略不寫了
}
}
|
過程:
在構造函數里先將數據庫類文件include進來;
然后又通過new Db並傳入數據庫連接信息實例化db類;
之后getList方法就可以通過$this->_db來調用數據庫類,實現數據庫操作。
看上去我們實現了想要的功能,但是這是一個噩夢的開始,以后example1,example2,example3....越來越多的類需要用到db組件,如果都這么寫的話,萬一有一天數據庫密碼改了或者db類發生變化了,豈不是要回頭修改所有類文件?
ok,為了解決這個問題,工廠模式出現了,我們創建了一個Factory方法,並通過Factory::getDb()方法來獲得db組件的實例:
|
1
2
3
4
5
6
|
class
Factory {
public
static
function
getDb(){
include
"./Lib/Db.php"
;
return
new
Db(
"localhost"
,
"root"
,
"123456"
,
"test"
);
}
}
|
sample類變成:
|
1
2
3
4
5
6
7
8
9
10
|
class
example {
private
$_db
;
function
__construct(){
$this
->_db = Factory::getDb();
}
function
getList(){
$this
->_db->query(
"......"
);
//這里具體sql語句就省略不寫了
}
}
|
這樣就完美了嗎?再次想想一下以后example1,example2,example3....所有的類,你都需要在構造函數里通過Factory::getDb();獲的一個Db實例,實際上你由原來的直接與Db類的耦合變為了和Factory工廠類的耦合,工廠類只是幫你把數據庫連接信息給包裝起來了,雖然當數據庫信息發生變化時只要修改Factory::getDb()方法就可以了,但是突然有一天工廠方法需要改名,或者getDb方法需要改名,你又怎么辦?當然這種需求其實還是很操蛋的,但有時候確實存在這種情況,一種解決方式是:
我們不從example類內部實例化Db組件,我們依靠從外部的注入,什么意思呢?看下面的例子:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class
example {
private
$_db
;
function
getList(){
$this
->_db->query(
"......"
);
//這里具體sql語句就省略不寫了
}
//從外部注入db連接
function
setDb(
$connection
){
$this
->_db =
$connection
;
}
}
//調用
$example
=
new
example();
$example
->setDb(Factory::getDb());
//注入db連接
$example
->getList();
|
這樣一來,example類完全與外部類解除耦合了,你可以看到Db類里面已經沒有工廠方法或Db類的身影了。我們通過從外部調用example類的setDb方法,將連接實例直接注入進去。這樣example完全不用關心db連接怎么生成的了。
這就叫依賴注入,實現不是在代碼內部創建依賴關系,而是讓其作為一個參數傳遞,這使得我們的程序更容易維護,降低程序代碼的耦合度,實現一種松耦合。
這還沒完,我們再假設example類里面除了db還要用到其他外部類,我們通過:
|
1
2
3
4
|
$example
->setDb(Factory::getDb());
//注入db連接
$example
->setFile(Factory::getFile());
//注入文件處理類
$example
->setImage(Factory::getImage());
//注入Image處理類
...
|
我們沒完沒了的寫這么多set?累不累?
ok,為了不用每次寫這么多行代碼,我們又去弄了一個工廠方法:
|
1
2
3
4
5
6
7
8
9
|
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時變為:
|
1
2
|
$example
=Factory::getExample();
$example
->getList();
|
似乎完美了,但是怎么感覺又回到了上面第一次用工廠方法時的場景?這確實不是一個好的解決方案,所以又提出了一個概念:容器,又叫做IoC容器、DI容器。
我們本來是通過setXXX方法注入各種類,代碼很長,方法很多,雖然可以通過一個工廠方法包裝,但是還不是那么爽,好吧,我們不用setXXX方法了,這樣也就不用工廠方法二次包裝了,那么我們還怎么實現依賴注入呢?
這里我們引入一個約定:在example類的構造函數里傳入一個名為Di $di的參數,如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
example {
private
$_di
;
function
__construct(Di &
$di
){
$this
->_di =
$di
;
}
//通過di容器獲取db實例
function
getList(){
$this
->_di->get(
'db'
)->query(
"......"
);
//這里具體sql語句就省略不寫了
}
}
$di
=
new
Di();
$di
->set(
"db"
,
function
(){
return
new
Db(
"localhost"
,
"root"
,
"root"
,
"test"
);
});
$example
=
new
example(
$di
);
$example
->getList();
|
Di就是IoC容器,所謂容器就是存放我們可能會用到的各種類的實例,我們通過$di->set()設置一個名為db的實例,因為是通過回調函數的方式傳入的,所以set的時候並不會立即實例化db類,而是當$di->get('db')的時候才會實例化,同樣,在設計di類的時候還可以融入單例模式。
這樣我們只要在全局范圍內申明一個Di類,將所有需要注入的類放到容器里,然后將容器作為構造函數的參數傳入到example,即可在example類里面從容器中獲取實例。當然也不一定是構造函數,你也可以用一個 setDi(Di $di)的方法來傳入Di容器,總之約定是你制定的,你自己清楚就行。
