装饰器模式以及Laravel框架下的中间件应用
Laravel框架的中间件使用:从请求进来到响应返回,经过中间件的层层包装,这种场景很适合用到一种设计模式---装饰器模式。
装饰器模式的作用,多种外界因素改变对象的行为。使用继承的方式改变行为不太被建议。
装饰器模式,即是有多个要改变对象的东西(装饰类),这些装饰类均实现一个接口。每个类在实现的接口中再调用构造器(或是setter等方法)传过来的其他装饰类对象的实现。
简单用一段代码描述下:
<?php interface Decorate { function getInfo(); } /** * Class DecoateA * 初始化一个装饰对象 */ class DecoateA implements Decorate{ /** * 实现接口 */ public function getInfo() { echo __CLASS__; } } /** * Class DecoateB * 装饰DecoateA 的装饰类 */ class DecoateB implements Decorate{ /** * DecoateB constructor. * @param Decorate $dec * 构造器 传过来其他装饰类 */ public function __construct(Decorate $dec) { $this->dec = $dec; } /** * 实现接口 */ public function getInfo() { echo __CLASS__; $this->dec->getInfo(); } } $obj = new DecoateA();// 初始一个对象 $obj = new DecoateB($obj);// 装饰对象 $obj->getInfo();// DecoateBDecoateA
以上代码,可以简单理解装饰器模式。
再说Laravel的装饰器模式应用: (这里用静态方法模拟一下)
不像是上边的例子,把装饰类传过去,Laravel用到了大量的闭包Closure代替
一段代码
<?php interface Step { public static function go(Closure $next); } /** * Class FirstStep * 装饰一 */ class FirstStep implements Step { public static function go(Closure $next) { echo __CLASS__." start<br/>"; $next(); echo __CLASS__." end<br/>"; } } /** * Class SecondStep * 装饰二 */ class SecondStep implements Step { public static function go(Closure $next) { echo __CLASS__." start<br/>"; $next(); echo __CLASS__." end<br/>"; } } function goFun($step, $className) { return function () use ($step, $className) { return $className::go($step); }; } function then() { $step = ['FirstStep', 'SecondStep']; $prepare = function () { echo "_init_"."<br/>"; }; call_user_func(array_reduce($step, "goFun", $prepare)); } then(); /** SecondStep start FirstStep start _init_ FirstStep end SecondStep end **/
这里需要理解下 array_reduct()的用法,第一个参数是被循环的数组,第二个是回调函数(其中回调函数的第一个参数是每次该函数上一次的return值,否则是array_reduce的第三个参数的初始值),第三个参数是初始值。
对于then() 的内部实现,可能开始理解比较困难。可以分拆一下实现:如下代码
// 初始闭包 $initFun = function (){ echo "_init_"."<br/>"; }; // 相当于array_reduce 第一次执行 $obj = function () use ($initFun) { return FirstStep::go($initFun); }; // 相当于array_reduce 第二次执行。go传递的都是个闭包函数 SecondStep::go($obj);