可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。

image.png

实现方式

  1. 声明仅有一个执行方法的命令接口

    1. /**
    2. * The Command interface declares a method for executing a command.
    3. */
    4. interface Command {
    5. execute(): void;
    6. }
  2. 抽取请求并使之成为实现命令接口的具体命令类。每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用。所有这些变量的数值都必须通过命令构造函数进行初始化 ```typescript /**

    • Some commands can implement simple operations on their own. */ class SimpleCommand implements Command { private payload: string;

      constructor(payload: string) { this.payload = payload; }

      public execute(): void { console.log(SimpleCommand: See, I can do simple things like printing (${this.payload})); } }

/**

  • However, some commands can delegate more complex operations to other objects,
  • called “receivers.” */ class ComplexCommand implements Command { private receiver: Receiver;

    /**

    • Context data, required for launching the receiver’s methods. */ private a: string;

      private b: string;

      /**

    • Complex commands can accept one or several receiver objects along with
    • any context data via the constructor. */ constructor(receiver: Receiver, a: string, b: string) { this.receiver = receiver; this.a = a; this.b = b; }

      /**

    • Commands can delegate to any methods of a receiver. */ public execute(): void { console.log(‘ComplexCommand: Complex stuff should be done by a receiver object.’); this.receiver.doSomething(this.a); this.receiver.doSomethingElse(this.b); } } ```
  1. 找到担任发送者职责的类。在这些类中添加保存命令的成员变量。发送者只能通过命令接口与其命令进行交互。发送者自身通常并不创建命令对象,而是通过客户端代码获取 ```typescript /**

    • The Receiver classes contain some important business logic. They know how to
    • perform all kinds of operations, associated with carrying out a request. In
    • fact, any class may serve as a Receiver. */ class Receiver { public doSomething(a: string): void { console.log(Receiver: Working on (${a}.)); }

      public doSomethingElse(b: string): void { console.log(Receiver: Also working on (${b}.)); } }

  1. 4. 修改发送者使其执行命令,而非直接将请求发送给接收者
  2. ```typescript
  3. /**
  4. * The Invoker is associated with one or several commands. It sends a request to
  5. * the command.
  6. */
  7. class Invoker {
  8. private onStart: Command;
  9. private onFinish: Command;
  10. /**
  11. * Initialize commands.
  12. */
  13. public setOnStart(command: Command): void {
  14. this.onStart = command;
  15. }
  16. public setOnFinish(command: Command): void {
  17. this.onFinish = command;
  18. }
  19. /**
  20. * The Invoker does not depend on concrete command or receiver classes. The
  21. * Invoker passes a request to a receiver indirectly, by executing a
  22. * command.
  23. */
  24. public doSomethingImportant(): void {
  25. console.log('Invoker: Does anybody want something done before I begin?');
  26. if (this.isCommand(this.onStart)) {
  27. this.onStart.execute();
  28. }
  29. console.log('Invoker: ...doing something really important...');
  30. console.log('Invoker: Does anybody want something done after I finish?');
  31. if (this.isCommand(this.onFinish)) {
  32. this.onFinish.execute();
  33. }
  34. }
  35. private isCommand(object): object is Command {
  36. return object.execute !== undefined;
  37. }
  38. }
  1. 客户端必须按照以下顺序来初始化对象:
    1. 创建接收者
    2. 创建命令,如有需要可将其关联至接收者
    3. 创建发送者并将其与特定命令关联 ```typescript /**
    • The client code can parameterize an invoker with any commands. */ const invoker = new Invoker(); invoker.setOnStart(new SimpleCommand(‘Say Hi!’)); const receiver = new Receiver(); invoker.setOnFinish(new ComplexCommand(receiver, ‘Send email’, ‘Save report’));

invoker.doSomethingImportant();

  1. <a name="lxHcw"></a>
  2. # 实例
  3. <a name="RZZ4n"></a>
  4. ## 自定义快捷键
  5. ```typescript
  6. interface Command {
  7. exec(): void
  8. }
  9. class CopyCommand implements Command {
  10. editor: Editor
  11. constructor(editor: Editor) {
  12. this.editor = editor
  13. }
  14. exec() {
  15. const { editor } = this
  16. editor.clipboard = editor.text.slice(...editor.range)
  17. }
  18. }
  19. class CutCommand implements Command {
  20. editor: Editor
  21. constructor(editor: Editor) {
  22. this.editor = editor
  23. }
  24. exec() {
  25. const { editor } = this
  26. editor.clipboard = editor.text.slice(...editor.range)
  27. editor.text = editor.text.slice(0, editor.range[0]) + editor.text.slice(editor.range[1])
  28. }
  29. }
  30. class PasteCommand implements Command {
  31. editor: Editor
  32. constructor(editor: Editor) {
  33. this.editor = editor
  34. }
  35. exec() {
  36. const { editor } = this
  37. editor.text = editor.text.slice(0, editor.cursorIndex) + editor.clipboard + editor.text.slice(editor.cursorIndex)
  38. }
  39. }
  40. type Editor = {
  41. cursorIndex: number;
  42. range: [number, number];
  43. text: string;
  44. clipboard: string;
  45. }
  46. const editor: Editor = {
  47. cursorIndex: 0,
  48. range: [0, 1],
  49. text: 'some text',
  50. clipboard: ''
  51. }
  52. type Keymap = { [key: string]: Command }
  53. class Hotkey {
  54. keymap: Keymap = {}
  55. constructor(keymap: Keymap) {
  56. this.keymap = keymap
  57. }
  58. call(e: KeyboardEvent) {
  59. const prefix = e.ctrlKey ? 'ctrl+' : ''
  60. const key = prefix + e.key
  61. this.dispatch(key)
  62. }
  63. dispatch(key: string) {
  64. this.keymap[key].exec()
  65. }
  66. }
  67. const keymap = {
  68. 'ctrl+x': new CutCommand(editor),
  69. 'ctrl+c': new CopyCommand(editor),
  70. 'ctrl+v': new PasteCommand(editor)
  71. }
  72. const hotkey = new Hotkey(keymap)
  73. document.onkeydown = (e) => {
  74. hotkey.call(e)
  75. }

上面的 hotkey 是 Invoker,editor 是 Receiver。
Redux 也是应用了命令模式,Store 相当于 Receiver,Action 相当于 Command,Dispatch 相当于 Invoker

参考资料

  1. 命令模式
  2. TS 实践中的命令模式