一、概述

命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化。命令模式的核心思想是将方法调用、请求或操作封装到一个单独的对象中,并给这个对象提供执行操作和撤销操作的方法。

在命令模式中,发送者(Invoker)与接收者(Receiver)之间完全解耦,发送者只需要知道如何发送命令,而不需要知道命令是如何被接收者执行的。

二、作用

命令模式的主要作用包括:

  • 解耦发送者和接收者:发送者不需要知道接收者的具体实现
  • 支持撤销操作:可以轻松实现命令的撤销和重做功能
  • 支持命令队列:可以将命令存储在队列中,按顺序执行
  • 支持宏命令:可以将多个命令组合成一个复合命令
  • 易于扩展:新增命令不需要修改现有代码,符合开闭原则

三、命令模式例子

1、菜单程序

命令模式最常见的应用场景之一就是菜单系统,每个菜单项对应一个命令:

// 接收者 - 文本编辑器
const createTextEditor = () => {
    let content = '';
    
    return {
        copy: () => {
            console.log('复制文本');
            return content;
        },
        paste: (text) => {
            content += text;
            console.log(`粘贴文本: ${text}`);
        },
        cut: () => {
            const text = content;
            content = '';
            console.log('剪切文本');
            return text;
        },
        getContent: () => content
    };
};

// 创建命令工厂函数
const createCopyCommand = (editor) => {
    let copiedText = '';
    
    return {
        execute: () => {
            copiedText = editor.copy();
            console.log('执行复制命令');
        },
        undo: () => {
            console.log('撤销复制命令');
        }
    };
};

const createPasteCommand = (editor, text) => {
    return {
        execute: () => {
            editor.paste(text);
            console.log('执行粘贴命令');
        },
        undo: () => {
            console.log('撤销粘贴命令');
        }
    };
};

// 调用者 - 菜单
const createMenu = () => {
    const commands = [];
    
    return {
        addCommand: (command) => {
            commands.push(command);
        },
        click: (index) => {
            if (commands[index]) {
                commands[index].execute();
            }
        }
    };
};

// 使用示例
const editor = createTextEditor();
const menu = createMenu();

menu.addCommand(createCopyCommand(editor));
menu.addCommand(createPasteCommand(editor, 'Hello World'));

menu.click(0); // 执行复制命令
menu.click(1); // 执行粘贴命令

2、撤销命令

命令模式非常适合实现撤销功能:

const createCalculator = () => {
    let value = 0;
    
    return {
        add: (num) => {
            value += num;
            console.log(`加上 ${num},当前值: ${value}`);
        },
        subtract: (num) => {
            value -= num;
            console.log(`减去 ${num},当前值: ${value}`);
        },
        getValue: () => value,
        setValue: (newValue) => {
            value = newValue;
        }
    };
};

const createAddCommand = (calculator, value) => {
    let previousValue = 0;
    
    return {
        execute: () => {
            previousValue = calculator.getValue();
            calculator.add(value);
        },
        undo: () => {
            calculator.setValue(previousValue);
            console.log(`撤销加法操作,恢复值: ${previousValue}`);
        }
    };
};

const createSubtractCommand = (calculator, value) => {
    let previousValue = 0;
    
    return {
        execute: () => {
            previousValue = calculator.getValue();
            calculator.subtract(value);
        },
        undo: () => {
            calculator.setValue(previousValue);
            console.log(`撤销减法操作,恢复值: ${previousValue}`);
        }
    };
};

const createCommandManager = () => {
    let history = [];
    let current = -1;
    
    return {
        execute: (command) => {
            // 执行新命令时,删除当前点之后的历史
            history = history.slice(0, current + 1);
            command.execute();
            history.push(command);
            current = history.length - 1;
        },
        undo: () => {
            if (current >= 0) {
                const command = history[current];
                command.undo();
                current--;
                return true;
            } else {
                console.log('没有可撤销的操作');
                return false;
            }
        },
        redo: () => {
            if (current < history.length - 1) {
                current++;
                const command = history[current];
                command.execute();
                return true;
            } else {
                console.log('没有可重做的操作');
                return false;
            }
        },
        canUndo: () => current >= 0,
        canRedo: () => current < history.length - 1
    };
};

// 使用示例
const calculator = createCalculator();
const commandManager = createCommandManager();

commandManager.execute(createAddCommand(calculator, 10));      // 值变为10
commandManager.execute(createSubtractCommand(calculator, 3)); // 值变为7
commandManager.execute(createAddCommand(calculator, 5));      // 值变为12

commandManager.undo(); // 撤销加5,值变为7
commandManager.undo(); // 撤销减3,值变为10
commandManager.redo(); // 重做减3,值变为7

3、命令队列

命令模式支持将命令存储在队列中按顺序执行:

const createCommandQueue = () => {
    const commands = [];
    let isRunning = false;
    
    const executeNext = () => {
        if (commands.length > 0) {
            isRunning = true;
            const command = commands.shift();
            command.execute(() => {
                isRunning = false;
                executeNext(); // 执行下一个命令
            });
        }
    };
    
    return {
        add: (command) => {
            commands.push(command);
            if (!isRunning) {
                executeNext();
            }
        }
    };
};

const createAsyncCommand = (action, delay) => {
    return {
        execute: (callback) => {
            console.log(`开始执行命令: ${action}`);
            setTimeout(() => {
                console.log(`完成命令: ${action}`);
                callback();
            }, delay);
        }
    };
};

// 使用示例
const queue = createCommandQueue();
queue.add(createAsyncCommand('下载文件', 1000));
queue.add(createAsyncCommand('处理数据', 2000));
queue.add(createAsyncCommand('保存结果', 1500));

4、宏命令

宏命令是将多个命令组合成一个复合命令:

const createMacroCommand = () => {
    const commands = [];
    
    return {
        add: (command) => {
            commands.push(command);
        },
        execute: () => {
            commands.forEach(command => command.execute());
        },
        undo: () => {
            // 逆序撤销
            for (let i = commands.length - 1; i >= 0; i--) {
                commands[i].undo();
            }
        }
    };
};

// 使用示例
const macro = createMacroCommand();
macro.add(createCopyCommand(editor));
macro.add(createPasteCommand(editor, 'Hello'));
macro.add(createPasteCommand(editor, ' World'));

macro.execute(); // 依次执行所有命令

5、傻瓜命令

傻瓜命令是指命令对象只需要调用execute方法,不需要接收者参与:

const createSimpleCommand = (action) => {
    return {
        execute: () => {
            action();
        }
    };
};

// 使用示例
const command = createSimpleCommand(() => {
    console.log('执行简单操作');
});
command.execute();

6、智能命令

智能命令是指命令对象自己处理请求,不需要额外的接收者:

const createSmartCommand = (process) => {
    return {
        execute: () => {
            console.log('智能命令自己处理请求');
            process();
        }
    };
};

const smartCommand = createSmartCommand(() => {
    console.log('处理具体业务逻辑');
});
smartCommand.execute();

四、Vue实现案例

案例一:通用命令管理器

<template>
  <div class="command-demo">
    <h2>通用命令管理器</h2>
    <div class="actions">
      <button @click="executeCommand('increment')">增加</button>
      <button @click="executeCommand('decrement')">减少</button>
      <button @click="executeCommand('reset')">重置</button>
      <button @click="undo" :disabled="!canUndo">撤销</button>
      <button @click="redo" :disabled="!canRedo">重做</button>
    </div>
    <div class="display">
      当前值: {{ value }}
    </div>
  </div>
</template>

<script>
export default {
  name: 'CommandManager',
  data() {
    return {
      value: 0,
      history: [],
      current: -1
    }
  },
  computed: {
    canUndo() {
      return this.current >= 0;
    },
    canRedo() {
      return this.current < this.history.length - 1;
    }
  },
  methods: {
    // 创建各种命令
    createIncrementCommand() {
      const previousValue = this.value;
      return {
        execute: () => {
          this.value++;
        },
        undo: () => {
          this.value = previousValue;
        }
      };
    },
    
    createDecrementCommand() {
      const previousValue = this.value;
      return {
        execute: () => {
          this.value--;
        },
        undo: () => {
          this.value = previousValue;
        }
      };
    },
    
    createResetCommand() {
      const previousValue = this.value;
      return {
        execute: () => {
          this.value = 0;
        },
        undo: () => {
          this.value = previousValue;
        }
      };
    },
    
    // 执行命令
    executeCommand(type) {
      let command;
      
      switch (type) {
        case 'increment':
          command = this.createIncrementCommand();
          break;
        case 'decrement':
          command = this.createDecrementCommand();
          break;
        case 'reset':
          command = this.createResetCommand();
          break;
        default:
          return;
      }
      
      // 清除当前点之后的历史
      this.history = this.history.slice(0, this.current + 1);
      command.execute();
      this.history.push(command);
      this.current = this.history.length - 1;
    },
    
    // 撤销操作
    undo() {
      if (this.current >= 0) {
        const command = this.history[this.current];
        command.undo();
        this.current--;
      }
    },
    
    // 重做操作
    redo() {
      if (this.current < this.history.length - 1) {
        this.current++;
        const command = this.history[this.current];
        command.execute();
      }
    }
  }
}
</script>

<style scoped>
.command-demo {
  max-width: 400px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.actions {
  margin-bottom: 20px;
}

.actions button {
  margin: 5px;
  padding: 8px 16px;
  border: 1px solid #999;
  border-radius: 4px;
  cursor: pointer;
}

.actions button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.display {
  font-size: 18px;
  text-align: center;
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 4px;
}
</style>

案例二:通用操作队列

<template>
  <div class="operation-queue">
    <h2>操作队列示例</h2>
    <div class="controls">
      <button @click="addOperation('operation1')">操作1</button>
      <button @click="addOperation('operation2')">操作2</button>
      <button @click="addOperation('operation3')">操作3</button>
      <button @click="clearQueue">清空队列</button>
    </div>
    <div class="status">
      <p>队列状态: {{ isRunning ? '执行中' : '空闲' }}</p>
      <p>队列长度: {{ queue.length }}</p>
    </div>
    <div class="log">
      <h3>操作日志:</h3>
      <ul>
        <li v-for="(log, index) in logs" :key="index">{{ log }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'OperationQueue',
  data() {
    return {
      queue: [],
      isRunning: false,
      logs: []
    }
  },
  methods: {
    // 创建异步操作命令
    createAsyncOperation(name, duration) {
      return {
        execute: (callback) => {
          this.logs.push(`开始执行 ${name}`);
          setTimeout(() => {
            this.logs.push(`完成 ${name}`);
            callback();
          }, duration);
        }
      };
    },
    
    // 添加操作到队列
    addOperation(type) {
      let command;
      
      switch (type) {
        case 'operation1':
          command = this.createAsyncOperation('操作1', 1000);
          break;
        case 'operation2':
          command = this.createAsyncOperation('操作2', 1500);
          break;
        case 'operation3':
          command = this.createAsyncOperation('操作3', 800);
          break;
        default:
          return;
      }
      
      this.queue.push(command);
      this.logs.push(`添加 ${type} 到队列`);
      
      if (!this.isRunning) {
        this.executeNext();
      }
    },
    
    // 执行队列中的下一个操作
    executeNext() {
      if (this.queue.length > 0) {
        this.isRunning = true;
        const command = this.queue.shift();
        command.execute(() => {
          this.isRunning = false;
          this.executeNext(); // 执行下一个命令
        });
      }
    },
    
    // 清空队列
    clearQueue() {
      this.queue = [];
      this.logs.push('队列已清空');
    }
  }
}
</script>

<style scoped>
.operation-queue {
  max-width: 500px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.controls button {
  margin: 5px;
  padding: 10px 15px;
  border: 1px solid #999;
  border-radius: 4px;
  cursor: pointer;
}

.status {
  margin: 15px 0;
  padding: 10px;
  background-color: #f0f0f0;
  border-radius: 4px;
}

.status p {
  margin: 5px 0;
}

.log {
  margin-top: 20px;
}

.log ul {
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid #ddd;
  padding: 10px;
  border-radius: 4px;
}

.log li {
  margin: 5px 0;
  padding: 3px 0;
  border-bottom: 1px solid #eee;
}
</style>

五、总结

优点

  • 降低耦合度:发送者和接收者完全解耦,符合迪米特法则
  • 易于扩展:新增命令类非常容易,符合开闭原则
  • 支持撤销操作:可以轻松实现命令的撤销和重做功能
  • 支持命令队列:可以将命令存储在队列中,实现批量执行
  • 支持宏命令:可以将多个命令组合成一个复合命令
  • 易于记录日志:可以方便地记录命令执行历史

缺点

  • 增加系统复杂性:每个命令都需要创建一个具体类,可能会导致类数量增加
  • 内存开销:为了实现撤销功能,需要存储命令执行前的状态
  • 代码量增加:相比直接调用方法,命令模式需要更多的代码

适用场景

命令模式适用于以下场景:

  1. 需要参数化对象以执行不同的请求
  2. 需要将请求排队或记录请求日志
  3. 需要支持撤销操作
  4. 系统需要在不同时间指定、排列和执行请求
  5. 系统需要将操作组合成宏命令

通过合理运用命令模式,可以构建更加灵活、可维护的应用程序,特别是在需要支持撤销操作和复杂命令处理的场景中。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐