装饰者模式
一步一步来,这个还是好理解的,至少没有工厂模式中的递归。
基础例子
先假设一个情景,这样比较好理解下面的代码,小芳接到电话要出门,出门前后都有一系列动作,各个动作都可以是一个单独的类,表示完成不同的任务。在各个动作中,都要能设置该动作执行的前后顺序。
实际代码
<?php
//接口类,decorator 装饰
interface Decorator{
public function display();
}
//处理响应的类 XiaoFang
class XiaoFang implements Decorator{
private $name;
public function __construc($name){
$this->name=$name;
}
public function display(){
echo "我要出门了";
}
}
//装饰类,也就是来实现上述功能的 finery 华丽的服饰
class Finery implements Decorator{
private $component;
public function __construct(Decorator $component){
$this->component=$component;
}
public function display(){
$this->component->display();
}
}
//下面开始就是各个动作类了
class Shoes extends Finery{
public function display(){
echo "穿上鞋子";
parent::display();
}
}
class Skirt extends Finery{
public function display(){
echo "穿上裙子";
parent::display();
}
}
class Fire extends Finery{
public function display(){
echo "出门前先整理头发";
parent::display();
echo "出门后再整理头发";
}
}
//下面就是重点了,前面设定好的调用顺序需要下面实例化过程的配合来实现
$xiaofang=new XiaoFang("小芳");
$shoes=new Shoes($xiaofang);
$skirt=new Skirt($shoes);
$fire=new Fire($skirt);
$fire->display();
?>
最后的输出结果顺序就是:
- 出门前先整理头发
- 穿上裙子
- 穿上鞋子
- 我出门了
- 出门后再整理一下头发
代码解析
$fire->display();
public function display(){
echo "出门前先整理头发";
parent::display();
echo "出门后再整理头发";
}
这里有一个parent::display();
,那么Fire
类的parent
类是什么呢?
首先要看Finery
类的构造函数:
public function __construct(Decorator $component){
$this->component=$component;
}
所有动作类均继承自该类,所以实例化类的时候会传入Decorator
类,各动作类均在其中调用了parent::display();
方法,该方法会调用起父类的display
方法,即Finery
的display
方法:
public function display(){
$this->component->display();
}
可以看到这里调用了实例化类时传入的Decorator
类的display
方法。那么问题就来了,实例化Fire
类时,我们往其中传入了什么参数呢?
$skirt=new Skirt($shoes);
$fire=new Fire($skirt);
是Skirt
类,而Skirt
类中的display
方法:
public function display(){
echo "穿上裙子";
parent::display();
}
所以输出结果是
- 出门前先整理头发=>
$fire->display();
- 穿上裙子=>
$skirt->display();
剩下的解析方式就是一样的了,这里就不再重复了。
进阶例子
前期知识储备
先把下面两个函数弄懂怎么用,否则你看不懂下面的代码的!
- array_reduce()
实例代码:
<?php
function sum($carry, $item)
{
$carry += $item;
return $carry;
}
function product($carry, $item)
{
$carry *= $item;
return $carry;
}
$a = array(1, 2, 3, 4, 5);
$x = array();
var_dump(array_reduce($a, "sum")); // int(15)
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"
?>
- call_user_func()
简化代码
interface Step{
public static function go(Closure $next);
}
class FirstStep implements Step{
public static function go(Closure $next){
echo "程序开始运行";
$next();
echo "程序结束运行";
}
}
function goFun($step,$className){
return function() use ($step,$className){
return $className::go($step);
};
}
function then(){
$steps=['FirstStep'];
$prepare=function(){ echo "中间运行过程"; };
$go=array_reduce($steps,"goFun",$prepare);
$go();
}
then();
设计思路跟上面是一样的,有点不同的是这里都是采用匿名函数的方式,这样做具体的目的我猜是为了跟工厂模式中采用闭包函数返回类实例有关,但是也只能是猜猜了。
代码解析
首先是array_reduce()
,这个函数,会将$prepare
作为第一个参数,$steps
数组的各个值作为参数传递进goFun
函数中,因为$steps
数组只有一个值,所以这里只介绍一个了:
function goFun(Closure $prepare,'FirstStep'){
return function() use (Closure $prepare,'FirstStep'){
return FirstStep::go(Closure $prepare);
}
}
接着就是FirstStep
的go
方法了:
public static function go(Closure $prepare){
echo "程序开始运行";
$prepare=function(){ echo "中间运行过程"; };
echo "程序结束运行";
}
源码简化版本
下面的代码是实际代码的简化版本:
<?php
interface Middleware{
public static function handle(Closure $next);
}
class VerifyCsrfToken implements Middleware{
public statis function handle(Closure $next){
echo "验证Csrf-Token";
$next();
}
}
class ShareErrorsFromSession implements Middleware{
public static function handle(Closure $next){
echo "如果session中有`error`变量,则共享它";
$next();
}
}
class StartSession implements Middleware{
public static function handle(Closure $next){
echo "开启Session,获取数据";
$next();
echo "保存数据,关闭session";
}
}
function getSlice(){
return function($stack,$pipe){
return function() use($stack,$pipe){
return $pipe::handle($stack);
}
}
}
function then(){
$pipe=[
'VerifyCsrfToken',
'ShareErrorFromSession',
'StartSession'
];
$firstSlice=function(){
echo "在这里将请求向路由器传递,因为在各个中间件中已经设置了传递前后执行的代码";
};
call_user_func(array_reduce($pipe,getSlice(),$firstSlice));
}
可以看到跟简化代码的设计思路基本是一致的。
Laravel中实际运行流程
/public/index.php
//生成处理响应的类
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//处理响应
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
其中Illuminate\Contracts\Http\Kernel::class
是一个接口类:
namespace Illuminate\Contracts\Http;
interface Kernel{
public function handle($request);
}
为了实例化该接口类,根据服务容器的依赖自动解决,可以知道实例了App\Http\Kernel::class
类,再来看该类:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// \App\Http\Middleware\CheckAge::class
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
// 'checkage'=>[\App\Http\Middleware\CheckAge::class]
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
// 'checkage'=>\App\Http\Middleware\CheckAge::class
];
}
该类中只是定义了一堆的中间件,但是这里有一个新的类use Illuminate\Foundation\Http\Kernel as HttpKernel;
,既然在App\Http\Kernel::class
类中没有定义handle
方法,那么该方法一定定义在它继承的类中,果然,在它继承的类中找到了handle
方法,但是在看handle
方法之前,需要先看其父类的构造函数:
//根据服务容器的依赖注入会自动加载
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
//middlewarePriority 这个值在子类中没有进行重写,应该是核心中间件吧
$router->middlewarePriority = $this->middlewarePriority;
//这个就是子类中的中间件组
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}
//子类中好像对该部分也进行了重写
foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}
下面是handle
方法。
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
event(new Events\RequestHandled($request, $response));
return $response;
}
具体的内容暂时不深究,这里就看一个点:$this->sendRequestThroughRouter($request);
,这个请求的源码如下:
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}