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

1姐军、裝飾者模式

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

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

以Laravel框架為例夸浅,在解析請求生成響應(yīng)之前或之后需要經(jīng)過中間件的處理仑最,主要包括驗證維護(hù)模式、Cookie加密帆喇、開啟會話警医、CSRF保護(hù)等,而這些處理有些是在生成響應(yīng)之前坯钦,有些是在生成響應(yīng)之后预皇,在實際開發(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."我出門了M患铡鲁豪!"."<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)用父類的同名方法郭宝,就可以實現(xiàn)重新組織打扮過程辞槐,實現(xiàn)打扮步驟的增加或減少,例如加一件衣服粘室、化個妝等榄檬。這就是裝飾者模式的應(yīng)用場景。

2衔统、請求處理管道

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

<?php

    /*定義一個 中間件接口*/
    interface Middllware
    {
        public static function handle(Closure $next); 
    }
    
    /*定義一個 csrf驗證類*/
    class VerifyCsrfToken implements Middleware
    {
        public static function handle(Closure $next)
        {
            echo "驗證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 "報錯數(shù)據(jù)碴开,關(guān)閉session"."<br>";
        }
    }
    
    /*定義一個 添加cookie隊列至響應(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'變量叹螟,則共享它
    * 驗證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ù)組處理冈闭,其實也可以使用 array_reduce(),因為 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ù)生成整個處理流程的過程還是比較難以理解的矛绘,我們這里把它簡單化耍休,用一個簡單的實例演示下,代碼如下:

<?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ù),其實就相當(dāng)于$go()的自我調(diào)用罩锐。以上我們應(yīng)該很清晰地理解了裝飾者模式以請求處理管道的方式實現(xiàn)的設(shè)計思路了奉狈。通過將復(fù)雜的東西簡化一下便于我們更好的理解。在Laravel框架中涩惑,請求處理管道主要是通過Illuminate\Pipeline\Pipeline類實現(xiàn)的仁期,感興趣的朋友可以參考下源碼。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蟀拷,隨后出現(xiàn)的幾起案子碰纬,更是在濱河造成了極大的恐慌,老刑警劉巖问芬,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悦析,死亡現(xiàn)場離奇詭異,居然都是意外死亡此衅,警方通過查閱死者的電腦和手機(jī)强戴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挡鞍,“玉大人骑歹,你說我怎么就攤上這事∧ⅲ” “怎么了道媚?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翘县。 經(jīng)常有香客問我最域,道長,這世上最難降的妖魔是什么锈麸? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任镀脂,我火速辦了婚禮,結(jié)果婚禮上忘伞,老公的妹妹穿的比我還像新娘薄翅。我一直安慰自己,他們只是感情好氓奈,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布翘魄。 她就那樣靜靜地躺著,像睡著了一般舀奶。 火紅的嫁衣襯著肌膚如雪暑竟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天伪节,我揣著相機(jī)與錄音光羞,去河邊找鬼绩鸣。 笑死拓劝,一個胖子當(dāng)著我的面吹牛扁誓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼淑倾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤铐炫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蒜焊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倒信,經(jīng)...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年泳梆,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳖悠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡优妙,死狀恐怖乘综,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情套硼,我是刑警寧澤卡辰,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布,位于F島的核電站邪意,受9級特大地震影響九妈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抄罕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一允蚣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呆贿,春花似錦嚷兔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竟块,卻和暖如春壶运,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浪秘。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工蒋情, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耸携。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓棵癣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夺衍。 傳聞我的和親對象是個殘疾皇子狈谊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評論 2 349

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