设计模式之命令模式

发布时间 2023-12-12 21:16:17作者: 当时明月在曾照彩云归

1. 定义

将一个请求封装成一个对象,从而允许客户端参数化不同的请求、将请求排队或者记录请求日志、以及支持可撤销的操作

2. 口语化表述

假设某餐厅的工作流程如下:

  • 顾客在大堂点餐,服务员记录菜单
  • 服务员将菜单送到后厨
  • 后厨根据菜单做菜
  • 服务员根据菜单送到对应的餐桌

在这个场景中,后厨不需要了解顾客做了什么,顾客不需要关心后厨做了什么,只需要按照菜单来做事情

假设顾客在点完餐后又想更改,只需要服务员更改一下对应的菜单即可

这种模式就是命令模式,这里的菜单就是一个请求对象,包含了请求信息

(下面的表述会沿用这个场景)

3. 源码示例

Cesium.js是一个著名的、基于WebGL的GIS前端框架

基于WebGL就不得不提WebGL(OpenGL)的基础概念,比如VAO(顶点数组对象)、VBO(顶点缓冲对象)、Shader Program(着色器)等

每一次渲染,通常都需要执行一系列的WebGL命令(WebGL是个全局状态机,需要使用GL命令控制),如 gl.clearColor(清屏)、gl.draw(绘制)等

Cesium.js在渲染模块中,将GL命令封装为Command对象

Cesium中的Command对象包含执行的指令参数和执行方法,比如最简单的ClearCommand

function ClearCommand(options) {
  // ...
  this.color = options.color;
  this.depth = options.depth;
  this.stencil = options.stencil;
  this.renderState = options.renderState;
  this.framebuffer = options.framebuffer;
  this.owner = options.owner;
  this.pass = options.pass;
}
 
ClearCommand.prototype.execute = function (context, passState) {
  context.clear(this, passState);
};
 

ClearCommand包含颜色、深度、通道等指令参数和执行方法context.clear(this, passState)

context.clear()会执行清除的WebGL指令:

Context.prototype.clear = function (clearCommand, passState) {
  // ...
  const c = clearCommand.color;
  const d = clearCommand.depth;
  const s = clearCommand.stencil;
 
  gl.clearColor(c.red, c.green, c.blue, c.alpha);
  gl.clearDepth(d);
  gl.clearStencil(s);
 
  bindFramebuffer(this, framebuffer);
  gl.clear(bitmask);
};

ClearCommand在Scene中的调用流程是:

初始化Scene时初始化ClearCommand

function Scene(options) {
  // ...
  this._clearColorCommand = new ClearCommand({
    color: new Color(),
    stencil: 0,
    owner: this,
  });
  // ...
}

执行更新时调用ClearCommandexecute()方法

Scene.prototype.updateAndExecuteCommands = function (passState, backgroundColor) {
    // ...
    updateAndClearFramebuffers(this, passState, backgroundColor);
    // ...
};
function updateAndClearFramebuffers(scene, passState, clearColor) {
  // ...
  // Clear the pass state framebuffer.
  const clear = scene._clearColorCommand;
  Color.clone(clearColor, clear.color);
  clear.execute(context, passState);
  // ...
}

这么设计的好处是,Command对象可以存储各种参数信息,这些信息也可以随时更改,并且,这个命令在在需要调用时只需要执行自己的execute方法

具体的Command(如,ClearCommand)只需要根据参数做事情,Scene对象只需要保存这些参数并在需要时执行Command的execute方法

缺点呢,存储Command对象需要内存开销,开发时封装Command对象需要时间

4. 总结

4.1 设计优点

  • 开闭原则

    新来的顾客只需要添加菜单即可,新来的厨师只需要处理菜单即可

  • 单一职责原则

    顾客、厨师只负责做自己的事情

  • 实现操作的拦截或者延迟执行

    菜单传递到后厨并没有立刻做出菜品,所以顾客可以修改菜单,厨师也可以提前做准备

4.2 适用场景

  • 通过操作来参数化对象

    将命令封装为一个对象,可以实时改变它的状态

  • 将操作放入队列中、操作的执行或者远程执行操作

  • 实现操作回滚功能

    即撤销上一步命令

5. 参考资料

[1] 命令设计模式 (refactoringguru.cn)

[2] Cesium渲染模块之Command - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)