關於php中依賴注入(DI)和控制反轉(IOC)的理解
一、什麼是依賴注入和控制反轉
1.依賴注入(DI)— Dependecy Injection
為了更方便的理解,我們把依賴注入分開理解,首先什麼是依賴?顧名思義,依賴就是各元件之間的一種關係。一般來說,在面向物件程式設計中,我們在類A中 使用到了 類B的例項,我們就可以說A依賴B,B是A的依賴。傳統的寫法就是在A類中直接呼叫B的例項。這種寫法會形成強耦合,不能保證A類的純潔性,所以並不是我們理想的設計模式。
class A { public function getAData() { $b = new B(); $data = $b->getBData(); //... return $data; } } class B { public function getBData() { //... } }
那什麼是注入什麼,由誰注入呢?注入就是我們實現解耦的一種方法,我們把A對B這種依賴關係,通過各種方式由第三方注入進來,而不是A直接對B進行操作,這個第三方就是IOC容器。下邊是兩種常用的注入方式
a. 由建構函式注入:
class A { private $objectB; //由建構函式注入 public function __construct($objectB) { $this->objectB = $objectB; } public function getAData() { $data = $this->objectB->getBData(); //... return $data; }
b. 由setter方法注入:
class A {
private $objectB;
//由setter方法注入
public function setObjectB($objectB){
$this->objectB = $objectB;
}
public function getAData() {
$data = $this->objectB->getBData();
//...
return $data;
}
}
2.控制反轉(IOC)— Inversion Of Control
說完依賴注入我們可以發現,本來是兩個物件A\B之間的事情,現在變成了三個物件。沒錯,多出來的這個就是容器,它就是控制AB依賴關係的。現在我們理解一下控制反轉這個概念,就會變得非常容易,控制就是對依賴關係的控制權,反轉是原本這個控制權是在A內部程式碼實現的,現在變成由外部容器實現然後注入進來,這就是所謂的反轉。依賴注入是控制反轉的具體實現,這兩個概念說的其實一回事,只是角度不同。
二、IOC容器的具體實現
在講IOC容器實現之前,我們先說一下解耦。實際應用中,大多時候A類依賴的不僅僅是B,還有C\D\E等等好多的類,這些被依賴的類,會在A類中的各種各樣的方法中使用。
class A {
public function getAData() {
$objectB = new B();
$dataB = $objectB->getBData();
$objectC = new C();
$dataC = $objectC->getCData();
//...
}
public function getAData2() {
$objectB = new B();
//...
}
}
class B {
public function getBData() {
//...
}
}
class C {
public function getCData() {
//...
}
}
這樣就會出現一個問題,假設某一天我們灑脫的B類不喜歡自己的名字了,它想改名為2B,畢竟2B更2。這個時候A類心裡苦,但是沒有辦法,那就都改了吧。我們當然知道這樣肯定是不合理,那這種情況怎麼解決呢?
工廠模式:
很簡單,工廠類的作用就是幫你批量的例項化那些依賴的類,這樣你再也不用在你的主類中使用new了。
class Factory {
static public function getObjectB() {
return new B();
}
static public function getObjectC() {
return new C();
}
}
class A {
private $objectB;
private $objectC;
public function getAData() {
$this->objectB = Factory::getObjectB();
$dataB = $this->objectB->getBData();
$this->objectC = Factory::getObjectC();
$dataC = $this->objectC->getCData();
//...
}
}
這樣我們就把B、C兩個類從我們的主類A中淨化了出去。這樣的過程就是解耦,我們把A和B\C\D..的耦合,轉變成了跟Factory一個類之間的耦合。以後不管B想改成什麼,我們都不要管,我們只要找Factory就行了。
似乎解決了大量耦合的問題,到此為止了嗎?當然不是,畢竟在我們的主類A中,還存在著一個感覺很突兀的Factory,換名字的問題仍然存在,這是一個很大的隱患。所以為了防止Factory換名字,我們得做點防護措施。
class A {
private $objectB;
private $objectC;
public function setObjectB($instance) {
$this->objectB = $instance;
}
public function setObjectC($instance) {
$this->objectC = $instance;
}
public function getAData() {
$dataB = $this->objectB->getBData();
$dataC = $this->objectC->getCData();
//...
}
}
這樣A類終於變的很純潔了,看不到B\C,也沒有了Factory。我們使用A類的時候可以這麼寫
$a = new A();
$a->setObjectB(Factory::getObjectB());
$a->setObjectC(Factory::getObjectC());
$a->getAData();
現在再來看A類,乾乾淨淨,沒有依賴,Factory這個原本被依賴的物件是通過引數,從A類外部注入進來的,這個過程就是依賴注入的實現。這種方式雖然實現了依賴注入,但是會有個問題,A類中要寫多個setObject方法,每次外部呼叫A 的時候,也必須要呼叫setObject方法好多次。如果A類依賴了十多個外部類,那我們每次使用A的時候豈不是很噁心。
class A {
private $objectB;
private $objectC;
private $objectD;
//...
public function setObjectB($instance) {
$this->objectB = $instance;
}
public function setObjectC($instance) {
$this->objectC = $instance;
}
public function setObjectD($instance) {
$this->objectD = $instance;
}
//...
public function getAData() {
$dataB = $this->objectB->getBData();
$dataC = $this->objectC->getCData();
$dataD = $this->objectD->getDData();
//...
}
}
$a = new A();
$a->setObjectB(Factory::getObjectB());
$a->setObjectC(Factory::getObjectC());
$a->setObjectD(Factory::getObjectD());
//...
$a->getAData();
這樣太不合理了,貌似我們可以再加個工廠類封裝一下,幫助使用者把setObject的工作封裝一下。
//把A類再封裝一下
class HeadFactory {
public static function allSet() {
$a = new A();
$a->setObjectB(Factory::getObjectB());
$a->setObjectC(Factory::getObjectC());
$a->setObjectD(Factory::getObjectD());
return $a;
}
}
//所有使用A類的地方,就可以直接這麼使用,不在每次都setObject
$app = HeadFactory::allSet();
$app->getAData();
$app->getAData2();
這下我們使用A的時候,也變得很方便了,依賴注入總算是完成了。
說了這麼多工廠類,那麼容器呢?好像並沒有講到容器是怎麼實現的。對比一下工廠類,工廠是把依賴的類註冊到靜態方法上的,而IOC容器是把這些依賴類註冊到靜態陣列中的。
class Di {
protected static $objectArr;
static public function set($k,$obj) {
self::$objectArr[$k] = $obj;
}
static public function get($k){
return self::$objectArr[$k];
}
}
class A {
private $di;
public function __construct($di)
{
$this->di = $di;
}
public function getAData() {
$dataB = $this->di->get("B")->getBData();
$dataC = $this->di->get("C")->getCData();
//...
}
}
使用的方式是這樣
Di::set("B", Factory::getObjectB());
Di::set("C", Factory::getObjectC());
$app = new A($DI);
$a->getAData();
我們一般會在專案初始化的時候,把需要注入的類全部都set好,這樣我們可以在整個
專案中都可以直接使用類似$this->di->get()這樣的方式來獲取到依賴的物件。phalcon,lavaral等框架就是類似的依賴注入實現。為了最後的優雅,我們要消滅所有的new,我們把主類A也加入到Factory類靜態方法中
class Factory {
static public function getDi(){
return new Di();
}
static public function getObjectA($di) {
return new A($di);
}
static public function getObjectB() {
return new B();
}
static public function getObjectC() {
return new C();
}
}
這樣我們最後的使用A類的方式就變成了這樣
class Di {
protected static $objectArr;
static public function set($k,$obj) {
self::$objectArr[$k] = $obj;
}
static public function get($k){
return self::$objectArr[$k];
}
}
class Factory {
static public function getDi(){
return new Di();
}
static public function getObjectA($di) {
return new A($di);
}
static public function getObjectB() {
return new B();
}
}
class A {
private $di;
public function __construct(Di & $di)
{
$this->di = $di;
}
public function getAData() {
$dataB = $this->di->get("B")->getBData();
//...
}
}
class B {
public function getBData() {
//...
print_r("getBData");
}
}
$di = Factory::getDi();
$di::set("B",Factory::getObjectB());
$app = Factory::getObjectA($di);
$app->getAData();