裝飾者模式在Laravel框架中的實(shí)現(xiàn)思路

1、裝飾者模式

裝飾者模式是在開放——關(guān)閉原則下實(shí)現(xiàn)動態(tài)添加或減少功能的一種方式。

說明:裝飾者模式就是不修改原類代碼和繼承的情況下動態(tài)擴(kuò)展類的功能。傳統(tǒng)的編程模式都是子類繼承父類實(shí)現(xiàn)方法重載,使用裝飾器模式,只需添加一個新的裝飾器對象,更加靈活,避免類數(shù)量和層次過多。

以Laravel框架為例,在解析請求生成響應(yīng)之前或之后需要經(jīng)過中間件的處理,主要包括驗(yàn)證維護(hù)模式、Cookie加密、開啟會話、CSRF保護(hù)等,而這些處理有些是在生成響應(yīng)之前,有些是在生成響應(yīng)之后,在實(shí)際開發(fā)過程中有可能還需要添加新的處理功能,如果再不修改原有類的基礎(chǔ)上動態(tài)地添加或減少處理功能將使框架可擴(kuò)展性大大增強(qiáng), 而這種需求正好可以被裝飾者模式解決。下面簡單的給出一個裝飾者模式的示例。

<?php 
    interface Decrator
    {
        public function display();
    }
    
    /*定義一個裝飾者xiaofang*/
    class XiaoFang implements Decrator
    {
        private $name;
        public function __construct($name)
        {
            $this->name = $name;
        }
        public function display()
        {
            echo "我是".$this->name."我出門了!!"."<br/>";
        }
    }
    
    /*定義一個服飾類*/
    class Finery implements Decrator
    {
        private $component;
        public function __construct(Decrator $component)
        {
            $this->component = $component;
        }
        public function display()
        {
            $this->component->display();
        }
    }
    
    /*定義一個鞋子類*/
    class Shoes extends Finery
    {
        public function display()
        {
            echo "穿上鞋子"."<br>";
            parent::display();
        }
    }
    
    /*定義一個裙子類*/
    class Skirt extends Finery
    {
        public function display()
        {
            echo '穿上裙子'."<br/>";
            parent::display();
        }
    }
    
    /*定義一個發(fā)型類*/
    class Hair extends Finery
    {
        public function display()
        {
            echo '出門前先整理頭發(fā)'.'<br/>';
            parent::display();
            echo '出門后再整理一下頭發(fā)'."<br/>";
        }

    }

    $xiaofang = new XiaoFang('小芳');
    $shoes = new Shoes($xiaofang);
    $skirt = new Skirt($shoes);
    $hair = new Hair($skirt);
    $hair->display();
    // 輸出:
    // 出門前先整理頭發(fā)
    // 穿上裙子
    // 穿上鞋子
    // 我是小芳,我出門了
    // 出門后再整理一下頭發(fā)
 ?>

我們假設(shè)小芳接到一個電話算是請求,而小芳出門是對請求的響應(yīng),那么在小芳出門前后要對自己進(jìn)行打扮,對應(yīng)于Laravel框架中,這些打扮的步驟就相當(dāng)于中間件的功能,而小芳出門是對請求的真正響應(yīng)。在小芳的打扮的過程中,可以隨時增加新的打扮類,只要該類繼承Finery類(裝飾類)并調(diào)用父類的同名方法,就可以實(shí)現(xiàn)重新組織打扮過程,實(shí)現(xiàn)打扮步驟的增加或減少,例如加一件衣服、化個妝等。這就是裝飾者模式的應(yīng)用場景。

2、請求處理管道

如上所示增加或者減少功能需要重新組織相應(yīng)過程,比如new Shoes 和 new Skirt順序互換,也就是實(shí)例化相應(yīng)類的順序,我們在這里是手動實(shí)現(xiàn)的,但是在laravel中我們知道它是通過服務(wù)容器進(jìn)行自動實(shí)例化的,它的服務(wù)容器就是解決這些依賴注入的自動化設(shè)備,實(shí)例之間的功能調(diào)用也是通過閉包函數(shù)完成的,這里為了將問題簡單化,我們通過靜態(tài)函數(shù)來避免實(shí)例化的過程,只模擬一下通過閉包函數(shù)來完成裝飾者模式,實(shí)現(xiàn)請求的處理管道。在Laravel框架中,針對請求的處理過程一共使用三次處理管道,我們先來看一段管道代碼:

<?php

    /*定義一個 中間件接口*/
    interface Middllware
    {
        public static function handle(Closure $next); 
    }
    
    /*定義一個 csrf驗(yàn)證類*/
    class VerifyCsrfToken implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "驗(yàn)證Csrf-Token"."<br/>";
            $next();
        }   
            
    }
    
    /*定義一個 錯誤分享類*/
    class ShareErrorsFromSession implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "如果session中有'errors'變量,則共享它"."<br/>";
            $next();
        }
    }
    
    /*定義一個 開啟session類*/
    class StartSession implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "開啟session,獲取數(shù)據(jù)"."<br/>";
            $next();
            echo "報(bào)錯數(shù)據(jù),關(guān)閉session"."<br>";
        }
    }
    
    /*定義一個 添加cookie隊(duì)列至響應(yīng) 類*/
    class AddQueuedCookiesToResponse implements Middleware
    {
        public static function handle(Closure $next)
        {
            $next();
            echo "添加下一次請求需要的cookie"."<br/>";
        }
    }
    
    /*定義一個 加密cookie類*/
    class EncryptCookies implements  Middleware
    {
        public static function handle(Closure $next)
        {
            echo "對輸入請求的cookie進(jìn)行解密"."<br/>";
            $next();
            echo "對輸出響應(yīng)的cookie進(jìn)行加密"."<br/>";
        }
    }
    
    /*定義一個 檢測維護(hù)狀態(tài)類*/
    class ChecKForMaintenanceMode implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "確定當(dāng)前程序是否處于維護(hù)狀態(tài)"."<br/>";
            $next();
        }
    }
    
    function getSlice()
    {
        return function($stack $pipe)
        {
            return function() use ($stack $pipe)
            {
                return $pipe::handle($stack);     
            };
        };
    }
    
    function then()
    {
        $pipes = [
            "ChecKForMaintenanceMode",
            "EncryptCookies",
            "AddQueuedCookiesToResponse",
            "StartSession",
            "ShareErrorsFromSession",
            "VerifyCsrfToken"
        ];
        $firstSlice = function() {
            echo "請求向路由器傳遞,返回響應(yīng)"."<br/>";
        };
        $pipes = array_reverse($pipes);  
        call_user_func(array_reduce($pipes, getSlice(), $firstSlice)); 
        // array_reduce()向用戶自定義函數(shù)發(fā)送數(shù)組中的值,并返回一個字符串:
        then();
    }
   /**輸出:
    * 確定當(dāng)前程序是否處于維護(hù)狀態(tài)
    * 對輸入請求的cookie進(jìn)行解密
    * 開啟session,獲取數(shù)據(jù)
    * 如果session中有'errors'變量,則共享它
    * 驗(yàn)證Csrf-Token
    * 請求向路由器傳遞,返回響應(yīng)
    * 保存數(shù)據(jù),關(guān)閉session
    * 添加下一次請求需要的cookie
    * 對輸出響應(yīng)的cookie進(jìn)行加密
    */    
?>

我們來看一下這個函數(shù)array_reduce($arr, $callback, initial) 使用回調(diào)函數(shù)迭代地將數(shù)組簡化為單一的值。
其中$arr為輸入的數(shù)組,$callback($result, $value)接受兩個參數(shù),$result為上一次迭代產(chǎn)生的值,$value是當(dāng)前迭代的值。簡單地講就是向用戶自定義函數(shù)發(fā)送數(shù)組中的值,并返回一個字符串。使用array_reduce()替代foreach()循環(huán)最常用的業(yè)務(wù)場景也許就是數(shù)組求和了,比如:

  • 注意 如果指定第三個參數(shù),則該參數(shù)將被當(dāng)成是數(shù)組中的第一個值來處理,或者如果數(shù)組為空的話就作為最終返回值。
<?php 
    $arr = array('1', '2', '3'); 
    $sum = 0;
    foreach ($arr as $v) {
        $sum += $v;   // echo $sum 求和完成
    }
    echo array_reduce($arr, function ($result, $v) {
        return $result + $v;   // 使用array_reduce()迭代求和
    })
?>

再比如,在某些業(yè)務(wù)場景下,我們從數(shù)據(jù)庫中查詢出一組數(shù)據(jù),需要將他們拼接成類似 (1,2,3,4,5)的 字符串,然后在 “SELECT * WHERE id in(1,2,3,4,5) ” 處理,這時候完全可以 foreach() 數(shù)組處理,其實(shí)也可以使用 array_reduce(),因?yàn)?array_reduce()就是“迭代地將數(shù)組簡化為單一的值”,如下

$arr = array(
    array("id"=>1,'name'=>"a"),
    array("id"=>2,"name"=>"c"),
    array("id"=>3,"name"=>"d")
);
    echo array_reduce($arr , function ($result, $v) {
        return ltrim($result.','.$v['id'],',');
      });

回到之前的代碼可以看到輸出的內(nèi)容是laravel框架對請求處理的部分流程,大部分與我們上面的裝飾者模式形式是不是很相似,但以上通過回調(diào)函數(shù)生成整個處理流程的過程還是比較難以理解的,我們這里把它簡單化,用一個簡單的實(shí)例演示下,代碼如下:

<?php
    interface Step
    {
        public static function go(Closure $next);
    }
    
    class FirstStep implements Step
    {
        public static function go(Closure $next)
        {
            echo "開啟session,獲取數(shù)據(jù)"."<br/>";
            $next();
            echo "保存數(shù)據(jù),關(guān)閉session"."<br/>";
        }
    }
    
    function goFun($step, $className)
    {
        return function () use ($step, $className)
        {
            return $className::go($step);
        };
    }
    
    function then()
    {
        $step = ['FirstStep'];
        $prepare = function () {echo "請求向路由傳遞,返回響應(yīng)"."<br/>"};
        $go = array_reduce($step, "goFun", $prepare);
        $go();
    }
    
    then();
    輸出:
    開啟session,獲取數(shù)據(jù)
    請求向路由傳遞,返回響應(yīng)
    保存數(shù)據(jù),關(guān)閉session
    
?>

以上代碼我們將處理功能簡化為只需要一步,再次回到這個array_reduce($steps, "goFun", $prepare)函數(shù)和goFun($step, $className)函數(shù)上,通過前面的介紹我們已經(jīng)知道array_reduce()接收三個參數(shù),其中最后一個參數(shù)為可選參數(shù),也叫initial(初始)參數(shù),他會被當(dāng)做數(shù)組中第一個值來處理,如果數(shù)組為空則作為返回值。而我們在這里給第三個參數(shù)傳遞的是一個回調(diào)函數(shù)$prepare,它將請求向路由器繼續(xù)傳遞,返回我們需要的響應(yīng),而第一個參數(shù)數(shù)組則記錄了外層功能的類名,goFun()函數(shù)作為處理數(shù)組的回調(diào)函數(shù)。那么很明顯,array_reduce()最終返回的仍是一個回調(diào)函數(shù),即$go():

    $go = function () {
        return $FirstStep::go(function () {echo "請求向路由傳遞,返回響應(yīng)"."<br/>";});
    };

總結(jié):

之前的例子中,通過call_user_func()函數(shù)執(zhí)行這個回調(diào)函數(shù),其實(shí)就相當(dāng)于$go()的自我調(diào)用。以上我們應(yīng)該很清晰地理解了裝飾者模式以請求處理管道的方式實(shí)現(xiàn)的設(shè)計(jì)思路了。通過將復(fù)雜的東西簡化一下便于我們更好的理解。在Laravel框架中,請求處理管道主要是通過Illuminate\Pipeline\Pipeline類實(shí)現(xiàn)的,感興趣的朋友可以參考下源碼。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內(nèi)容