PHP 依賴註入(DI) 和 控制反轉(IoC)
要想理解 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 ( $name , static :: $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、依賴註入和控制反轉是同一概念嗎?
答:從上面可以看出:依賴註入是從應用程序的角度在描述,可以把依賴註入描述完整點:應用程序依賴容器創建並註入它所需要的外部資源;而控制反轉是從容器的角度在描述,描述完整點:容器控制應用程序,由容器反向的向應用程序註入應用程序所需要的外部資源。
參考:
Laravel 中的 依賴註入 與 控制反轉
PHP 依賴註入(DI) 和 控制反轉(IoC)