JavaScript ES6 中的命令模式

问题描述 投票:0回答:1

对于我正在开发的应用程序,我需要实现支持撤消/重做操作的命令模式。我还想使用 javascript ES6 新功能,如类、常量等。 我从来没有使用过这种模式因此我真的很想知道使用 javascript es6 语法可以很好地实现这种模式

在 Google 上阅读此模式后,我使用一个小示例编写了自己的实现,其中只有 2 种类型的命令(更新和删除)和一个简单的模型数据库。

欢迎大家批评指正!

模型DB.js

// mockupDB.js


export default {
    key1: "value1",
    key2: "value2",
    key3: "value3"
}

命令.js

// Command.js

import mockupDB from "./mockupDB";


class Command {

    constructor(execute, undo, serialize, value) {
        this.execute = execute;
        this.undo = undo;
        this.serialize = serialize;
        this.value = value;
    }

}



export function UpdateCommand(key, value) {

    let oldValue;

    const execute = () => {
        if (mockupDB.hasOwnProperty(key)) {
            oldValue = mockupDB[key];
            mockupDB[key] = value;
        }
    };

    const undo = () => {
        if (oldValue) {
            mockupDB[key] = oldValue;
        }
    };

    const serialize = () => {
        return JSON.stringify({type: "Command", action: "update", key: key, value: value});
    };

    return new Command(execute, undo, serialize, value);
}


export function DeleteCommand(key) {

    let oldValue;

    const execute = () => {
        if (mockupDB.hasOwnProperty(key)) {
            oldValue = mockupDB[key];
            delete mockupDB[key];
        }
    };

    const undo = () => {
        mockupDB[key] = oldValue;
    };

    const serialize = () => {
        return JSON.stringify({type: "Command", action: "delete", key: key});
    };

    return new Command(execute, undo, serialize);
}

CommandManager.js

// CommandManager.js

export class CommandManager {

    constructor() {
        this.executeHistory = [];
        this.undoHistory = [];
    }

    execute(command) {
        this.executeHistory.push(command);
        command.execute();
        console.log(`Executed command ${command.serialize()}`);
    }

    undo() {
        let command = this.executeHistory.pop();
        if (command) {
            this.undoHistory.push(command);
            command.undo();
            console.log(`Undo command ${command.serialize()}`)
        }
    }

    redo() {
        let command = this.undoHistory.pop();
        if (command) {
            this.executeHistory.push(command);
            command.execute();
            console.log(`Redo command ${command.serialize()}`);
        }
    }
}

client.js

// client.js

import {CommandManager} from "./CommandManager";
import {UpdateCommand, DeleteCommand} from "./Command";
import mockupDB from "./mockupDB";

"use strict";

const main = () => {

    const commandManager = new CommandManager();

    console.log("DB status", mockupDB, "\n");
    commandManager.execute(new UpdateCommand("key2", "newValue2"));
    commandManager.execute(new DeleteCommand("key3"));
    console.log("DB status", mockupDB, "\n");
    commandManager.undo();
    commandManager.undo();
    console.log("DB status", mockupDB, "\n");
    commandManager.redo();
    commandManager.redo();
    console.log("DB status", mockupDB, "\n");

};

main();

输出

DB status { key1: 'value1', key2: 'value2', key3: 'value3' }

Executed command {"type":"Command","action":"update","key":"key2","value":"newValue2"}
Executed command {"type":"Command","action":"delete","key":"key3"}
DB status { key1: 'value1', key2: 'newValue2' }

Undo command {"type":"Command","action":"delete","key":"key3"}
Undo command {"type":"Command","action":"update","key":"key2","value":"newValue2"}
DB status { key1: 'value1', key2: 'value2', key3: 'value3' }

Redo command {"type":"Command","action":"update","key":"key2","value":"newValue2"}
Redo command {"type":"Command","action":"delete","key":"key3"}
DB status { key1: 'value1', key2: 'newValue2' }
javascript design-patterns ecmascript-6 command
1个回答
0
投票

这是一个基于意见的问题。在我看来:

  • OOP 增加了代码量,对 JS 来说很麻烦,所以我不会对命令设计模式的每个角色(调用者、命令、接收者)都使用

    class
    。一个带有闭包的 JS 函数来表示命令对象就足够了

  • 您的实施中缺少一个角色 - receiver。您可以将满足接口的接收者对象传递给命令对象,而不是直接在命令对象中使用具体的接收者对象。这称为依赖倒置原则。命令对象和接收者对象应该依赖于抽象(接收者接口 - TypeScript 中的

    interface
    )而不是具体(接收者具体 - 类的实例)

从命令模式的wiki,我们知道:

命令对象知道接收器并调用接收器的方法。接收方方法的参数值存储在命令中。

接收者对象拥有由命令的执行方法调用的方法。

  • 最后一个建议是变量命名。将它们更改为
    redoStack
    undoStack
    可能会更好。

我的实施将是:

const updateCommand = (receiver, data) => {
  let oldValue;
  const execute = () => {
    oldValue = receiver.get(data.key);
    receiver.update(data.key, data.value);
  }
  const undo = () => {
    receiver.update(data.key, oldValue);
  }
  const toJSON = () => {
    return { type: "Command", action: "update", data }
  }
  return { execute, undo, toJSON }
}


const deleteCommand = (receiver, data) => {
  let oldValue;
  const execute = () => {
    oldValue = receiver.get(data.key);
    receiver.delete(data.key)
  }
  const undo = () => {
    receiver.create(data.key, oldValue);
  }
  const toJSON = () => {
    return { type: "Command", action: "delete", data }
  }
  return { execute, undo, toJSON }
}


const commandManager = {
  redoStack: [],
  undoStack: [],
  execute(command) {
    this.redoStack.push(command);
    command.execute();
    console.log(JSON.stringify(command));
  },
  undo() {
    const command = this.redoStack.pop();
    if (command) {
      this.undoStack.push(command);
      command.undo();
      console.log(JSON.stringify(command));
    }
  },
  redo() {
    const command = this.undoStack.pop();
    if (command) {
      this.redoStack.push(command);
      command.execute();
      console.log(JSON.stringify(command));
    }
  }
}


const fakeDB = {
  data: {
    key1: "value1",
    key2: "value2",
    key3: "value3"
  },
  get(key) {
    return this.data[key];
  },
  update(key, value) {
    this.data[key] = value;
  },
  delete(key) {
    delete this.data[key];
  },
  create(key, value) {
    this.data[key] = value;
  },
}
// other receivers with same interface
const fakeMongoDB = {}

function main() {
  console.log("DB status", fakeDB.data, "\n");
  commandManager.execute(updateCommand(fakeDB, { key: 'key2', value: 'newValue2' }));
  commandManager.execute(deleteCommand(fakeDB, { key: 'key3' }));
  console.log("DB status", fakeDB.data, "\n");
  commandManager.undo();
  commandManager.undo();
  console.log("DB status", fakeDB.data, "\n");
  commandManager.redo();
  commandManager.redo();
  console.log("DB status", fakeDB.data, "\n");
}

main();
© www.soinside.com 2019 - 2024. All rights reserved.