1. 程式人生 > >PHP依賴注入、控制反轉

PHP依賴注入、控制反轉

要想理解 PHP 依賴注入 和 控制反轉 兩個概念,就必須搞清楚如下的兩個問題:

  • DI —— Dependency Injection 依賴注入
  • IoC —— Inversion of Control 控制反轉

什麼是依賴注入

沒有你我就活不下去,那麼,你就是我的依賴。 說白了就是:

不是我自身的,卻是我需要的,都是我所依賴的。一切需要外部提供的,都是需要進行依賴注入的。

依賴注入舉例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

class Boy {

  protected $girl;

 

  public function __construct(Girl $girl) {

    $this->girl = $girl;

  }

}

 

class Girl {

  ...

}

 

$boy new Boy();  

// Error; Boy must have girlfriend!

 

// 所以,必須要給他一個女朋友才行

$girl new Girl();

 

$boy new Boy($girl); // Right! So Happy!

從上述程式碼我們可以看到Boy強依賴Girl必須在構造時注入Girl的例項才行。

那麼為什麼要有依賴注入這個概念,依賴注入到底解決了什麼問題?

我們將上述程式碼修正一下我們初學時都寫過的程式碼:

1

2

3

4

5

6

7

class Boy {

  protected $girl;

 

  public function __construct() {

    $this->girl = new Girl();

  }

}

這種方式與前面的方式有什麼不同呢?

我們會發現Boy的女朋友被我們硬編碼到Boy的身體裡去了。。。 每次Boy重生自己想換個型別的女朋友都要把自己扒光才行。

某天Boy特別喜歡一個LoliGirl ,非常想讓她做自己的女朋友。。。怎麼辦? 重生自己。。。扒開自己。。。把Girl扔了。。。把 LoliGirl塞進去。。。

1

2

3

4

5

6

7

8

9

10

11

12

class LoliGirl {

 

}

 

class Boy {

  protected $girl;

 

  public function __construct() {

      //  $this->girl = new Girl();  // sorry...

      $this->girl = new LoliGirl();

  }

}

某天 Boy迷戀上了御姐....Boy 好煩。。。

是不是感覺不太好?每次遇到真心相待的人卻要這麼的折磨自己。。。

Boy說,我要變的強大一點。我不想被改來改去的!

好吧,我們讓Boy強大一點:

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

interface Girl {

  // Boy need knows that I have some abilities.

}

 

class LoliGril implement Girl {

  // I will implement Girl's abilities.

}

 

class Vixen implement Girl {

  // Vixen definitely is a girl, do not doubt it.

}

 

class Boy {

  protected $girl;

 

  public function __construct(Girl $girl) {

    $this->girl = $girl;

  }

}

 

$loliGirl new LoliGirl();

$vixen new Vixen();

 

$boy new Boy($loliGirl);

$boy new Boy($vixen);

Boy 很高興,終於可以不用扒開自己就可以體驗不同的人生了。。。So Happy!

依賴注入方式

1、構造器 注入

1

2

3

4

5

6

7

8

<?php

class Book {

  private $db_conn;

  

  public function __construct($db_conn) {

    $this->db_conn = $db_conn;

  }

}

2、setter 注入

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

<?php

class Book {

    private $db;

    private $file;

 

    function setdb($db) {

        $this->db = $db;

    }

 

    function setfile($file) {

        $this->file = $file;

    }

}

 

class file {

}

 

class db {

}

 

// ...

 

class test {

    $book new Book();

    $book->setdb(new db());

    $book->setfile(new file());

}

小結:

因為大多數應用程式都是由兩個或者更多的類通過彼此合作來實現業務邏輯,這使得每個物件都需要獲取與其合作的物件(也就是它所依賴的物件)的引用。如果這個獲取過程要靠自身實現,那麼將導致程式碼高度耦合並且難以維護和除錯。

所以才有了依賴注入的概念,依賴注入解決了以下問題:

  • 依賴之間的解耦
  • 單元測試,方便Mock

上面倆種方法程式碼很清晰,但是當我們需要注入很多個依賴時,意味著又要增加很多行,會比較難以管理。

比較好的解決辦法是 建立一個class作為所有依賴關係的container,在這個class中可以存放、建立、獲取、查詢需要的依賴關係。先來了解一下IOC的概念

控制反轉 (Inversion Of Control, IOC)

控制反轉 是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做 依賴注入(Dependency Injection, DI), 還有一種叫"依賴查詢"(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?php

 

class Ioc {

    protected $db_conn;

 

    public static function make_book() {

        $new_book new Book();

        $new_book->set_db(self::$db_conn);

        //...

        //...

        //其他的依賴注入

        return $new_book;

    }

}

此時,如果獲取一個book例項,只需要執行$newone = Ioc::makebook();

以上是container的一個具體例項,最好還是不要把具體的某個依賴注入寫成方法,採用registry註冊,get獲取比較好

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

<?php

/**

 * 控制反轉類

 */

class Ioc {

    /**

     * @var array 註冊的依賴陣列

     */

    protected static $registry array();

 

    /**

     * 新增一個 resolve (匿名函式)到 registry 陣列中

     *

     * @param string  $name    依賴標識

     * @param Closure $resolve 一個匿名函式,用來建立例項

     * @return void

     */

    public static function register($name, Closure $resolve) {

        static::$registry[$name] = $resolve;

    }

 

    /**

     * 返回一個例項

     *

     * @param string $name 依賴的標識

     * @return mixed

     * @throws \Exception

     */

    public static function resolve($name) {

        if (static::registered($name)) {

            $name static::$registry[$name];

            return $name();

        }

 

        throw new \Exception("Nothing registered with that name");

    }

 

    /**

     * 查詢某個依賴例項是否存在

     *

     * @param string $name

     * @return bool

     */

    public static function registered($name) {

        return array_key_exists($namestatic::$registry);

    }

}

現在就可以通過如下方式來註冊和注入一個

1

2

3

4

5

6

7

8

9

10

11

<?php

Ioc::register("book"function () {

    $book new Book();

    $book->setdb('db');

    $book->setfile('file');

 

    return $book;

});

 

// 注入依賴

$book = Ioc::resolve('book');

問題彙總

1、參與者都有誰?

答:一般有三方參與者,一個是某個物件;一個是IoC/DI的容器;另一個是某個物件的外部資源。又要名詞解釋一下,某個物件指的就是任意的、普通的Java物件; IoC/DI的容器簡單點說就是指用來實現IoC/DI功能的一個框架程式;物件的外部資源指的就是物件需要的,但是是從物件外部獲取的,都統稱資源,比如:物件需要的其它物件、或者是物件需要的檔案資源等等。

2、依賴:誰依賴於誰?為什麼會有依賴?

答:某個物件依賴於IoC/DI的容器。依賴是不可避免的,在一個專案中,各個類之間有各種各樣的關係,不可能全部完全獨立,這就形成了依賴。傳統的開發是使用其他類時直接呼叫,這會形成強耦合,這是要避免的。依賴注入借用容器轉移了被依賴物件實現解耦。

3、注入:誰注入於誰?到底注入什麼?

答:通過容器向物件注入其所需要的外部資源

4、控制反轉:誰控制誰?控制什麼?為什麼叫反轉?

答:IoC/DI的容器控制物件,主要是控制物件例項的建立。反轉是相對於正向而言的,那麼什麼算是正向的呢?考慮一下常規情況下的應用程式,如果要在A裡面使用C,你會怎麼做呢?當然是直接去建立C的物件,也就是說,是在A類中主動去獲取所需要的外部資源C,這種情況被稱為正向的。那麼什麼是反向呢?就是A類不再主動去獲取C,而是被動等待,等待IoC/DI的容器獲取一個C的例項,然後反向的注入到A類中。

5、依賴注入和控制反轉是同一概念嗎?

答:從上面可以看出:依賴注入是從應用程式的角度在描述,可以把依賴注入描述完整點:應用程式依賴容器建立並注入它所需要的外部資源;而控制反轉是從容器的角度在描述,描述完整點:容器控制應用程式,由容器反向的嚮應用程式注入應用程式所需要的外部資源。