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稱之為次類,為了實現主類的目標,要依賴很多次類來配合。
好,現在你已經知道什么是依賴了。
問題來了,F在講正經的。
比方說你很忙,你不僅要泡妞,還要開公司,做生意,沒事打打高爾夫球什么的。這每一件事都是一個“主類”,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(){
$this->gift = Factory::BuyGifts();
$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->setGift(Factory::BuyGifts()); // 看到 Factory已經滾粗 我們的主類了
$paoniu->giveGifts();
現在Hookup類就像一個公司,作為老板的你只關心怎么泡妞,臟活累活交給別人干,于是你設立了一個setGift采購部門,這個部門專門負責和Factory中介打交道,這樣Factory中介就完全滾粗你的視野了。
Factory這個被依賴的對象 是通過參數 從外部 注入到 類內容的 靜態 屬性 實現實例化的,這個就是依賴注入。
可以聰明的你馬上發現,我擦,setGift 你這個部門就負責采購gift?我要打電話,發預約邀請難道還要setPhone,setInvitation嗎?
這樣主類里面要寫很多set方法,外面要調用很多次set方法,你會發現,更不科學了。
里面:
public function setGift($instance){
$this->gift = $instance;
public function setPhone($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->setPhone(Factory::GetPhone());
$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;
static function set($key, $object){
self::$objects[$key]=$object;
}
static function get ($key){
return self::$objects[$key];
unset(self::$objects[$key]);
}
}
----- Hookup.php ----------
require ‘BuyGifts.php';
require 'Factory.php ';
require 'Di.php ';
class Hookup {
private $di;
function __construct(Di &$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();
$di->set(‘BuyGift’, function(){
$paoniu=new Hookup($di);
$paoniu->giveGifts();
———-
可以看到,IoC模式才是真正的總代,它連公司內部的事情也管了。和設立set部門相比,set只能處理一個個單獨的業務,比如setGift,setPhone,setInvitation,之類的,,而ioc容器把這些實例全部放在一個數組里統一處理,并且還可以重新命名(鍵名),實現了完全的解耦。
請注意的是,我們在注冊Ioc容器的時候,是這樣寫的:
$di->set(‘BuyGift’, function(){
$di->set(‘BuyGift’, Factory::BuyGift());
第一個參數是數組的鍵名,第二個參數是數組的值;
第一種寫法用了回調函數,它只有在讀取數組的值的時候才會被執行;
第二種方法直接實例化成一個對象,保存到數組的值中。第二種方法會比較占資源。
另外我們發現
$di->set(‘BuyGift’, function(){
return Factory::BuyGift();
}
這里面怎么還有new呢,我們用靜態方法和Factory寫得優雅一些:
Di::set(‘BuyGift’, function(){
$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理解起來比較難,因為它的設計不是入門級別的,而是專業級別的,比前面講的要復雜許多,但是看官方文檔,寥寥數語,真有種想砸電腦的沖動。