詳解Laravel依賴注入(DI)和Ioc容器
Author:Woody
Laravel這個框架,用起來方便,理解起來不簡單。
為什麼不簡單?因為包含了一大堆所謂“先進”的概念,其中依賴注入(DI)和Ioc容器是比較核心的內容之一。
我百度了一下,講PHP DI和Ioc的內容很少,更別說詳解Laravel ioc的了。
在這裡,我綜合了幾篇寫得比較典型的文章,以一個產品經理的身份,從使用者體驗的角度嘗試讓初學者也能比較容易理解這個2個概念。
DI和Ioc就是一種設計模式,這個概念已經存在10幾年了,請先對面向物件程式設計和設計模式(DesignPatterns)有初步的理解:
預先了解一下這三種模式:1. 工廠模式 2. 單例模式 3. 註冊器模式
也請了解一下 interface 的用法和作用;
如果上述內容你都不是很熟悉,沒關係,很簡單的,請接著看:
1. 首先來看 依賴注入(DI):
故事是這樣的:
我們知道寫一個類的時候,類本身是有個目的的,類裡面有很多方法,每個方法搞定一些事情;
class Hookup {
public function getPhone(){
//此處省略
}
public function inviteHer(){
//此處省略
}
public function giveGifts(){
//此處省略
}
public function kissHer(){
//此處省略
}
}
比方說
這個類是什麼意思?Hookup就是泡妞的意思,裡面有4個步驟,第一步打電話,第二部邀請她共進晚餐,第三步送禮物,第四步打波兒,第五步.....我只能幫你到這裡了...
別看只有4步,其實每一步都不簡單,第一步打電話,你得先要到人家電話吧,第二步邀請吃飯,你得提前訂好飯店吧,第三步送禮物,你得先買禮物吧~第四步Kiss,你總得抓住一個合適的機會吧;
當你在專心處理Hookup的時候,我太瞭解你了,你只關心結果,也就是抱得美人歸,問電話號碼,訂餐,買禮物,找機會這種小事,就交給其他“類”處理吧。
如下:
require 'BuyGifts.php';
class Hookup {
public function getPhone(){
//此處省略
}
public function inviteHer(){
//此處省略
}
public function giveGifts(){
$gift->select();
$paoniu = new hookup();
$paoniu->giveGifts();
這裡面送禮物這個環節(比較懶,其他的不寫了),就引入了BuyGifts這個類,買禮物這種小事,就交給BuyGifts這個祕書去處理吧。
所以,一切問題的出發點,就是在一個類中呼叫其他類,我們要解決的,就是在這過程中會發生的問題。
Hookup這個可以稱之為主類,BuyGifts稱之為次類,為了實現主類的目標,要依賴很多次類來配合。
好,現在你已經知道什麼是依賴了。
問題來了。現在講正經的。
比方說你很忙,你不僅要泡妞,還要開公司,做生意,沒事打打高爾夫球什麼的。這每一件事都是一個“主類”,BuyGifts 這種“次類” ,除了泡妞,也可以放在 做生意這個類裡面呼叫,做生意也要送禮的嘛,當然還可以應用在別的主類。
如果BuyGifts被呼叫很多次,哪一天需要把名字改了,那你就必須在眾多主類中逐一修改。
還有就是,每一次呼叫都要new例項化一次。
這樣做不科學,你也知道。
怎麼辦: 工廠模式
工廠模式很簡單,就是來幫助你批量例項化“次類”的。也就是說,用了工廠模式,你的主類中將不會再出現new這樣的關鍵字。
看例項,有3個類 哦:
----- Factory.php ----------
include 'BuyGifts.php'
class Factory {
static function getPhone(){
return new getPhone;
}
static function BuyGifts(){
return new BuyGifts;
}
//……下面還有很多,此處省略
}
----- BuyGifts.php ----------
class BuyGifts{
public function select(){
//此處省略 }
public function pay(){
//此處省略 }
}
----- Hookup.php ----------
require 'BuyGifts.php';
require 'Factory.php ';
class Hookup {
private $gift;
public function getPhone(){
//此處省略
}
public function inviteHer(){
//此處省略
}
public function giveGifts(){
$gift->select();
$paoniu = new hookup();
$paoniu->giveGifts();
你看,現在主類Hookup 要呼叫次類BuyGifts,就得先去找Factory類,Factory就變成中介了。
Factory這個中介的主要服務就是幫 你例項化次類,另外管理很多次類(工廠嘛),這樣你就不用直接與次類發生關係。
這個過程就叫做 解耦,不管次類怎麼變,你找工廠就可以了。
可是這樣做問題依舊存在,,當我們在很多主類裡呼叫了工廠及其方法,可是有一天發現工廠類要改名,,或者工廠裡面的方法要改名呢?那我們還不是得逐一改主類?雖然這種情況不多,但是這種不講信譽的“黑中介”也是偶爾會出現的。
怎麼辦呢?我們對這個Factory 中介 也得 防 一手。
----- Hookup.php ----------
require 'BuyGifts.php';
require 'Factory.php ';
class Hookup {
private $gift;
public function getPhone(){
//此處省略
}
public function inviteHer(){
//此處省略
}
public function giveGifts(){
$gift->select();
$gift->pay();
}
public function kissHer(){
//此處省略
}
public function setGift($instance){
$this->gift = $instance;
}
}
$paoniu = new hookup();
$paoniu->giveGifts();
現在Hookup類就像一個公司,作為老闆的你只關心怎麼泡妞,髒活累活交給別人幹,於是你設立了一個setGift採購部門,這個部門專門負責和Factory中介打交道,這樣Factory中介就完全滾粗你的視野了。
Factory這個被依賴的物件 是通過引數 從外部 注入到 類內容的 靜態 屬性 實現例項化的,這個就是依賴注入。
可以聰明的你馬上發現,我擦,setGift 你這個部門就負責採購gift啊?我要打電話,發預約邀請難道還要setPhone,setInvitation嗎?
這樣主類裡面要寫很多set方法,外面要呼叫很多次set方法,你會發現,更不科學了。
裡面:
public function setGift($instance){
$this->phone = $instance;
}
public function setInvitation($instance){
$this->Invitation = $instance;
}
….
外面:
$paoniu->setGift(Factory::BuyGifts());
$paoniu->setPhone(Factory::GetPhone());
$paoniu->setInvitation(Factory::SendInvitation());
….
這個時候,你已經把Factory 趕出 Hookup公司了,所以,公司內部的事情,只能你自己搞定了。
公司外部的業務,這時Factory中介 又屁顛屁顛的跑過來找你,提出一個方案:
我們來搞個“總代”怎麼樣?
class TopFactory {
public static function all_Agents_in_one(){
$paoniu_agent->setGift(Factory::BuyGifts());
$paoniu_agent->setInvitation(Factory::SendInvitation());
return $paoniu_agent;
}
}
黑中介Factory對你說:“你看我搞了個總代公司(TopFactory),你在外面也不要new啊,set什麼的了,我全包了。
於是現在你在外面要做的變簡單了:
$paoniu = TopFactory::all_Agents_in_one();
$paoniu->giveGifts();//giveGifts裡面就可以呼叫Buygifts了;
$paoniu->getPhone();
….
到現在為止,你已經有一套完整的 依賴注入方案了,這個方案組建的時候雖然有點複雜(層層轉包),但是一旦建好,維護起來比較方便。
可是,別忘了公司內部的事情還沒解決哪,另外要提醒你,你是程式設計師,不是老闆,所以Factory那一部分也是你的活~~~
需要一種更高階的方案,讓程式設計師的生活變得Easier一些。
這個方案就是IoC容器,IoC容器首先是一種類註冊器,其次它是一種更高階的依賴注入方式。
它和工廠Factory其實性質一樣,都是中介代理,但實現機制不一樣。
工廠Factory 把 次類 一一對應 註冊到 類中的 例項化靜態方法中;
IoC容器是把 次類 例項化物件 依次 註冊到 類中一個靜態陣列;
IoC容器的設計模式叫做 註冊器模式;
看例項:
———Di.php——-
class Di {
protected static $objects;
self::$objects[$key]=$object;
}
static function get ($key){
unset(self::$objects[$key]);
}
}
----- Hookup.php ----------
require ‘BuyGifts.php';
require 'Factory.php ';
require 'Di.php ';
class Hookup {
private $di;
$this->_di = $di;
}
public function getPhone(){
//此處省略
}
public function inviteHer(){
//此處省略
}
public function giveGifts(){
$this->di->get(‘BuyGifts’)->select();
$this->di->get(‘BuyGifts’)->pay();
}
public function kissHer(){
//此處省略
}
public function setGift($instance){
$this->gift = $instance;
}
}
$di = new Di();
$paoniu=new Hookup($di);
$paoniu->giveGifts();
———-
可以看到,IoC模式才是真正的總代,它連公司內部的事情也管了。和設立set部門相比,set只能處理一個個單獨的業務,比如setGift,setPhone,setInvitation,之類的,,而ioc容器把這些例項全部放在一個數組裡統一處理,並且還可以重新命名(鍵名),實現了完全的解耦。
請注意的是,我們在註冊Ioc容器的時候,是這樣寫的:
$di->set(‘BuyGift’, Factory::BuyGift());
第一個引數是陣列的鍵名,第二個引數是陣列的值;
第一種寫法用了回撥函式,它只有在讀取陣列的值的時候才會被執行;
第二種方法直接例項化成一個物件,儲存到陣列的值中。第二種方法會比較佔資源。
另外我們發現
$di->set(‘BuyGift’, function(){
return Factory::BuyGift();
}
$paoniu=factory::Hookup(factory::DI()); // 將容器注入主類
$paoniu->giveGifts(); //這裡就可以呼叫BuyGift()這個次類(依賴類);
好了,我們來看看Laravel Ioc容器 是怎麼用的:
繫結類到容器
App::bind('foo', function()
{
return newFooBar;
});
App 就是Ioc容器,bind就是set方法,’foo’ 就是陣列中 例項的 鍵名;
至此,我們已經對Ioc容器有了一個比較清楚的認識,我們已經瞭解了工廠模式,註冊模式,依賴注入等知識,其實依賴注入有3種方式,,一種就是set注入(單類注入),一種是容器注入(Ioc),還有一種是介面注入(Interface);前兩種我們已經學習過了,後一種放在本文的第二部分來講。
在本文的第二部分,我們將詳細講解LaravelIoC的用法,為什麼說Laravel理解起來比較難,因為它的設計不是入門級別的,而是專業級別的,比前面講的要複雜許多,但是看官方文件,寥寥數語,真有種想砸電腦的衝動。