1. 程式人生 > >關於php中依賴注入(DI)和控制反轉(IOC)的理解

關於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();