返回介绍

命令模式( Command )

发布于 2025-01-25 22:50:14 字数 5250 浏览 0 评论 0 收藏 0

命令模式是在 Node.js 中另一个重要的设计模式。在其最通用的定义中,命令模式封装了主体对象信息,并对主体对象执行一个动作,而不是在主体对象上直接调用一个方法或一个函数,我们创建一个对象 invocation 执行这样一个 调用;那么实现这个意图将是另一个组件的责任,将其转化为实际行动。传统上,这个模式是围绕着四个主要的组件,如下图所示:

命令模式的典型组织可以描述如下:

  • Command :这是封装调用一个必要信息的对象方法或功能。
  • Client :这将创建该命令并将其提供给调用者。
  • Invoker :这是负责执行目标上的命令。
  • Target (或 Receiver ):这是调用的主题。它可以是一个单独的功能或对象的方法。

正如我们将看到的,这四个组件可以根据我们想要的方式变化很多实施模式;在这一点上,这听起来不是什么新鲜事。使用命令模式而不是直接执行一个操作有好几个。

优点和应用:

  • 命令可以安排在稍后执行。
  • 一个命令可以很容易地序列化并通过网络发送。这很简单,属性允许我们在远程机器上分配作业,传输命令
  • 从浏览器到服务器,创建 RPC 系统等等。
  • 通过命令可以很容易地在系统上保存所有执行的操作历史记录。
  • 命令是一些数据同步算法的重要组成部分和解决冲突。
  • 计划执行的命令如果尚未执行,则可以取消。它 也可以恢复(撤消),使应用程序的状态的重点 在命令执行之前。
  • 几个命令可以组合在一起。这可以用来创建原子交易或实施一个机制,从而在所有的操作组立即执行。
  • 可以对一组命令执行不同类型的转换,例如 作为重复删除,加入和拆分,或应用更复杂的算法如 Operational Transformation ( OT ),这是当今大多数的基础实时协作软件,如协同文本编辑。

前面的列表清楚地向我们展示了这种模式的重要性,特别是在 node.js 这样的平台中,网络和异步执行是必不可少的参与者。

灵活模式

正如我们已经提到的, JavaScript 中的命令模式可以通过许多不同的方式实现;我们现在只演示其中的几个,只是为了给出它的范围的概念。

任务模式

我们可以从最基本的和平凡的实现开始:任务模式。当然, JavaScript 中创建一个表示调用的对象的最简单方法是创建一个关闭:

function createTask(target, args) {
  return () => {
    target.apply(null, args);
  }
}

这看起来一点也不新鲜;我们已经在书中多次使用了这种模式,特别是在第 3 章,带有回调的异步控制流模式中。这种技术允许我们使用单独的组件来控制和调度任务的执行,这在本质上等同于命令模式的调用者。例如,您还记得我们是如何定义传递给异步库的任务的吗?或者更好的是,你还记得我们是如何结合使用发电机的吗?回调模式本身可以被认为是命令模式的一个非常简单的版本。

较复杂的命令模式

现在让我们来处理一个更复杂的命令的示例;这一次我们希望支持撤消和序列化。让我们从命令的目标开始,这个小对象负责向 Twitter 这样的服务发送状态更新。为了简单起见,我们使用这种服务的模拟:

const statusUpdateService = {
  statusUpdates: {},
  sendUpdate: function(status) {
    console.log('Status sent: ' + status);
    Design Patterns
      [252]
    let id = Math.floor(Math.random() * 1000000);
    statusUpdateService.statusUpdates[id] = status;
    return id;
  },
  destroyUpdate: id => {
    console.log('Status removed: ' + id);
    delete statusUpdateService.statusUpdates[id];
  }
};

现在,让我们创建一个命令来表示新状态更新的发布:

function createSendStatusCmd(service, status) {
  let postId = null;
  const command = () => {
    postId = service.sendUpdate(status);
  };
  command.undo = () => {
    if (postId) {
      service.destroyUpdate(postId);
      postId = null;
    }
  };
  command.serialize = () => {
    return {
      type: 'status',
      action: 'post',
      status: status
    };
  };
  return command;
}

前面的函数是一个工厂,它生成新的 sendstate 命令。每个命令实现以下三个功能:

  1. 命令本身是一个函数,当调用它时,它将触发操作;换句话说,它实现了我们前面看到的任务模式。该命令在执行时将使用目标服务的方法发送新的状态更新。
  2. 连接到主任务的 auto() 函数,该函数恢复操作的效果。在我们的例子中,我们只是调用目标服务上的 deadyupdate() 方法。
  3. serialize() 函数,它构建一个 json 对象,该对象包含重建同一个命令对象所需的所有信息。 在此之后,我们可以构建一个调用程序;我们可以通过实现它的构造函数和它的 run() 方法来开始:
    class Invoker {
    constructor() {
     this.history = [];
    }
    run(cmd) {
     this.history.push(cmd);
     cmd();
     console.log('Command executed', cmd.serialize());
    }
    }
    
    前面定义的 run() 方法是 Invoker 的基本功能;它负责将命令保存到 history 实例变量中,然后触发命令本身的执行。接下来,我们可以添加一个延迟执行命令的新方法:
    delay(cmd, delay) {
    setTimeout(() => {
     this.run(cmd);
    }, delay)
    }
    
    然后,我们可以实现一个 undo() 方法来恢复最后一个命令:
    undo() {
    const cmd = this.history.pop();
    cmd.undo();
    console.log('Command undone', cmd.serialize());
    }
    
    最后,我们还希望能够在远程服务器上运行命令,方法是使用 Web 服务序列化并通过网络传输命令:
    runRemotely(cmd) {
    request.post('http://localhost:3000/cmd', {
       json: cmd.serialize()
     },
     err => {
       console.log('Command executed remotely', cmd.serialize());
     }
    );
    }
    
    既然我们有了命令、调用程序和目标,唯一缺少的组件就是客户端。让我们从实例化 Invoker 开始:
    const invoker = new Invoker();
    
    然后,我们可以使用以下代码行创建一个命令:
    const command = createSendStatusCmd(statusUpdateService, 'HI!');
    
    现在我们有了一个命令,表示状态消息的发布;然后我们可以决定立即发送它:
    invoker.run(command);
    
    但是,我们犯了一个错误;让我们恢复到时间线的状态,就像发送最后一条消息之前的情况一样:
    invoker.undo();
    
    我们还可以决定从现在起一小时内发送消息:
    invoker.delay(command, 1000 * 60 * 60);
    
    或者,我们可以通过将任务迁移到另一台机器来分配应用程序的负载:
    invoker.runRemotely(command);
    

我们刚刚创建的一个小例子展示了如何在命令中包装一个操作可以打开一个可能性的世界,这只是冰山一角。

正如最后的讨论,值得注意的是,只有在真正需要的时候才会使用成熟的命令模式。事实上,我们看到了我们需要编写多少额外的代码来简单地调用 statuupdatesservice 方法;如果我们所需要的只是一个调用,那么一个复杂的命令就会被杀死。但是,如果我们需要安排任务的执行,或者运行异步操作,那么简单的任务模式提供了最好的折衷。如果相反,我们需要更高级的特性,如撤销支持、转换、冲突解决,或者我们前面描述的其他花哨用例之一,那么对命令使用更复杂的表示几乎是必要的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文