命令模式基础概念
命令模式(Command Pattern)是一种行为型设计模式,其核心思想是将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。命令模式将发起请求的对象(调用者)和执行请求的对象(接收者)解耦,通过命令对象作为中间层来协调两者。
命令模式的核心组件
- 命令接口(Command) - 定义执行操作的接口,通常包含
execute()
方法。 - 具体命令(ConcreteCommand) - 实现命令接口,持有接收者的引用,并调用接收者的相应方法。
- 接收者(Receiver) - 知道如何执行与请求相关的操作,负责具体业务逻辑。
- 调用者(Invoker) - 持有命令对象,触发命令的执行,不直接与接收者交互。
- 客户端(Client) - 创建具体命令对象并设置接收者,将命令对象传递给调用者。
命令模式的实现
下面通过一个简单的遥控器示例展示命令模式的实现:
// 1. 命令接口
interface Command {
void execute();
void undo(); // 可选:支持撤销操作
}
// 2. 接收者 - 电灯
class Light {
public void on() {
System.out.println("Light is on");
}
public void off() {
System.out.println("Light is off");
}
}
// 3. 具体命令 - 开灯命令
class LightOnCommand implements Command {
private Light light; // 持有接收者的引用
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on(); // 调用接收者的方法
}
@Override
public void undo() {
light.off(); // 撤销操作:调用相反的方法
}
}
// 4. 具体命令 - 关灯命令
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// 5. 调用者 - 遥控器
class RemoteControl {
private Command command; // 持有命令对象
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute(); // 触发命令执行
}
public void pressUndoButton() {
command.undo(); // 触发命令撤销
}
}
// 6. 客户端代码
public class CommandPatternClient {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
// 创建具体命令并关联接收者
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
// 创建调用者
RemoteControl remote = new RemoteControl();
// 设置命令并执行
remote.setCommand(lightOn);
remote.pressButton(); // 输出:Light is on
remote.setCommand(lightOff);
remote.pressButton(); // 输出:Light is off
// 使用撤销功能
remote.pressUndoButton(); // 输出:Light is on
}
}
命令模式的扩展应用
-
宏命令(Macro Command) - 组合多个命令,实现批处理:
class MacroCommand implements Command { private Command[] commands; public MacroCommand(Command[] commands) { this.commands = commands; } @Override public void execute() { for (Command cmd : commands) { cmd.execute(); } } @Override public void undo() { for (Command cmd : commands) { cmd.undo(); } } }
-
命令队列 - 实现请求的排队和异步执行:
class CommandQueue { private Queue<Command> queue = new LinkedList<>(); public void addCommand(Command command) { queue.add(command); } public void executeAll() { while (!queue.isEmpty()) { queue.poll().execute(); } } }
-
日志命令 - 记录命令历史,支持系统恢复:
class Logger { public void logCommand(Command command) { // 将命令写入日志文件 System.out.println("Logging command: " + command.getClass().getName()); } }
命令模式的应用场景
- 撤销 / 重做功能 - 如文本编辑器、图形设计工具的撤销操作
- 事务管理 - 数据库操作的批处理和回滚机制
- 任务队列 - 异步任务的调度和执行
- 远程调用 - 将请求封装为命令对象进行网络传输
- 菜单系统 - GUI 应用中的菜单命令,如 "复制"、"粘贴" 等
- 权限控制 - 通过命令对象控制对资源的访问权限
命令模式的优缺点
优点:
- 解耦调用者和接收者 - 调用者无需知道接收者的细节,降低耦合度
- 支持撤销操作 - 通过实现
undo()
方法可以轻松支持撤销功能 - 支持命令队列 - 可以将命令对象存储在队列中实现异步执行
- 符合开闭原则 - 可以轻松添加新的命令类,无需修改现有代码
- 支持日志和事务 - 可以记录命令日志,实现事务管理和系统恢复
缺点:
- 类数量增加 - 每个具体命令都需要一个类,可能导致类爆炸
- 实现复杂度 - 对于简单操作,使用命令模式可能过于繁琐
- 命令状态管理 - 如果命令需要维护状态(如参数),可能增加设计复杂度
- 性能开销 - 封装命令对象会带来额外的性能开销,尤其是简单操作
使用命令模式的注意事项
- 合理设计命令接口 - 根据需求确定命令接口的方法,通常至少包含
execute()
- 考虑命令的粒度 - 命令粒度不宜过大或过小,应根据业务逻辑合理划分
- 处理撤销操作 - 如果需要支持撤销,确保命令的
undo()
方法正确恢复状态 - 避免过度使用 - 对于简单的请求 - 响应场景,无需使用命令模式
- 命令的生命周期管理 - 注意命令对象的生命周期,避免内存泄漏
- 结合其他模式 - 命令模式常与工厂模式结合创建命令对象,与观察者模式结合实现事件通知
总结
命令模式通过将请求封装为对象,实现了请求的发送者和接收者之间的解耦,使系统更具灵活性和可扩展性。它支持命令的排队、记录、撤销等功能,广泛应用于需要处理多种请求、支持撤销操作或异步执行的场景。在实际开发中,命令模式常用于 GUI 系统、事务管理、任务调度等领域。合理使用命令模式可以提高代码的可维护性和复用性,但需要注意控制类的数量和实现复杂度。