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)的仁期,感興趣的朋友可以參考下源碼。