簡介
依賴注入 Dependency Injection 簡稱
DI
,目的是讓
代碼耦合度降低,子產品化程度高
,讓代碼更易測試
什麼是依賴
為什麼會有依賴?因為我們為了子產品化,把各種小功能都做成了一個子產品,子產品之間互相調用,這樣就産生了依賴。
耦合
一個好的代碼結構設計一定是松耦合的,這也是很多通用設計模式的宗旨,就是把分散在各處的同一個功能的代碼彙聚到一起,形成一個子產品,然後在不同子產品之間通過一些細小的、明确的管道進行溝通。
在實踐中,不同功能和子產品之間的互相依賴是不可避免的,而如何處理好這些依賴之間的關系則是代碼結構能否變得美好的關鍵。
沒有用依賴注入的情況
傳統的思路是應用程式用到一個User 類,就會建立User 類并調用User 類的方法
假如這個方法内需要一個Notify 類,就會建立Notify 類并調用Notify 類的方法,而這個方法内需要一個Email 類,
就會建立Email 類,接着做些其它工作。
//使用者類
class User{
public function register($user)
{
echo "使用者注冊 | ";
// 注冊操作
// 發送确認郵件
$notify = new Notify();
$notify->sendEmail('register', $user);
}
}
//通知類
class Notify{
public function sendEmail($type, $data) {
echo "發送通知 | ";
switch ($type) {
case 'register':
// 發送注冊确認郵件
$email = new Email($type);
$email->send($data);
}
}
}
//郵箱類
class Email{
public function send($data) {
echo "發送郵件 | ";
// 發送郵件
}
}
$user = new User();
$user->register('使用者:黎明強'); // 使用者注冊 | 發送通知 | 發送郵件 |
上述代碼中,三個類之間逐層依賴,三個類執行個體化的順序是
User -> Notify -> Email
。
也就是說我先執行個體化User類,可能執行了一些代碼之後再去執行個體化我需要的其他類,比如Notify,以此類推。
這種依賴會讓我們不得不為了得到需要的依賴而去做的一些準備工作,有時候可能一個new操作還不夠。
而這部分工作就是所說的耦合,他會讓一個獨立功能的類不得不去關心一些和自己的主體功能沒什麼關系的操作。
如何解除一個類對其他類的依賴?
要解決這個問題也很簡單,我可以
- 先執行個體化好Email類,
- 然後再執行個體化Notify,然後把Email對象作為參數傳給Notify
- 最後執行個體化User類,然後把Notify傳進去。
這就是所謂的依賴注入,可以看到這個過程中類執行個體化的順序完全反過來了,先執行個體化被依賴的對象,而不是先執行個體化最終需要的對象,這是**
控制反轉
**。
使用依賴注入
可以通過構造函數來注入需要的依賴,也可以用一些其他的方法。
代碼如下:
//使用者類
class User{
protected $notify;
public function __construct(Notify $notify)
{
$this->notify = $notify;
}
public function register($user)
{
echo "使用者注冊 | ";
$this->notify->sendEmail('register',$user);
}
}
//通知類
class Notify{
protected $email ;
public function __construct(Email $email)
{
$this->email = $email;
}
public function sendEmail($type, $data) {
echo "發送通知 | ";
switch ($type) {
case 'register':
// 發送注冊确認郵件
$this->email->send($data);
}
}
}
//郵箱類
class Email{
public function send($data) {
echo '發送郵件 | ';
// 發送郵件
}
}
//調用-------
$email = new Email();
$notify = new Notify($email);
$user = new User($notify);
$user->register('liming'); // 使用者注冊 | 發送通知 | 發送郵件 |
使用依賴注入的好處是顯而易見的,我們通過參數了讓 email對象通過參數傳到了 noitfy 類中,而不是在 noitfy 類中的方法中執行個體化 email 類,進而将 email類和 noitfy 類解耦 。
使用依賴注入容器來管理依賴
那又有新的問題,例子中隻有三個類還好,那如果這個User類依賴Notify來發郵件,依賴Model來存資料庫,依賴redis來緩存,這樣固然把依賴關系轉移到了類的外部,但還是會導緻我隻想執行個體化一下User的時候,卻要手動做很多的準備工作,會讓代碼混亂。
是以這個時候需要一個容器。而這個容器的作用就是替我來管理這些依賴。
1 、 定義容器類
這段代碼使用了
魔術方法
,在給不可通路屬性指派時,
- __set() 會被調用。讀取不可通路屬性的值時,
- __get() 會被調用。
// 容器
class Container
{
private $s = array();
function __set($k, $c)
{
$this->s[$k] = $c;
}
function __get($k)
{
return $this->s[$k]($this);
}
}
在程式啟動的時候,我們可以在一個地方統一的注冊好一系列的基礎服務。
$c = new Container();
$c->email = function () {
return new Email();
};
$c->notify = function ($c) {
return new Notify($c->email);
};
$c->user = function ($c) {
return new User($c->notify);
};
// 從容器中取得user
$foo = $c->user;
$foo->register('liming'); // 使用者注冊 | 發送通知 | 發送郵件 |
這段代碼使用了匿名函數, 總之容器負責執行個體化,注入依賴,處理依賴關系等工作。
示範 :容器代碼
再來一段簡單的代碼示範一下,容器代碼
class IoC
{
protected static $registry = [];
public static function bind($name, Callable $resolver)
{
static::$registry[$name] = $resolver;
}
public static function make($name)
{
if (isset(static::$registry[$name])) {
$resolver = static::$registry[$name];
return $resolver();
}
throw new Exception('Alias does not exist in the IoC registry.');
}
}
IoC::bind('email', function () {
return new Email();
});
IoC::bind('noitfy', function () {
return new Notify(IoC::make('email'));
});
IoC::bind('user', function () {
return new User(IoC::make('noitfy'));
});
echo "<pre>";
// 從容器中取得User
$foo = IoC::make('user');
$foo->register('liming'); // 使用者注冊 | 發送通知 | 發送郵件 |
這段代碼使用了後期靜态綁定