命令模式详解:将请求封装为对象,实现解耦与扩展
在软件设计中,我们经常遇到需要将请求的发送者与接收者解耦的场景。命令模式(Command Pattern)正是为解决这类问题而生的一种行为型设计模式。它将请求封装成一个独立的对象,使得我们可以参数化客户端的不同请求,队列化或记录请求,并支持可撤销的操作。
什么是命令模式?
命令模式的核心思想是将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
模式结构
命令模式包含以下几个主要角色:
- Command(命令接口):声明执行操作的接口
- ConcreteCommand(具体命令):将一个接收者对象绑定于一个动作,实现执行方法
- Invoker(调用者):要求命令执行请求
- Receiver(接收者):知道如何实施与执行一个请求相关的操作
- Client(客户端):创建具体命令对象并设置其接收者
命令模式的实现
让我们通过一个实际的例子来理解命令模式的实现。假设我们正在开发一个智能家居控制系统,需要控制各种设备如灯光、空调等。
基础实现
首先,我们定义命令接口:
// 命令接口
public interface Command {
void execute();
void undo();
}
接下来,创建接收者类 - 具体的设备:
// 接收者 - 灯光
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " 灯光已打开");
}
public void off() {
System.out.println(location + " 灯光已关闭");
}
}
// 接收者 - 空调
public class AirConditioner {
private int temperature = 26;
public void on() {
System.out.println("空调已打开,当前温度:" + temperature);
}
public void off() {
System.out.println("空调已关闭");
}
public void setTemperature(int temperature) {
this.temperature = temperature;
System.out.println("空调温度设置为:" + temperature);
}
public int getTemperature() {
return temperature;
}
}
然后,实现具体命令类:
// 具体命令 - 灯光开关命令
public 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();
}
}
public 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();
}
}
// 具体命令 - 空调温度设置命令
public class SetTemperatureCommand implements Command {
private AirConditioner airConditioner;
private int newTemperature;
private int previousTemperature;
public SetTemperatureCommand(AirConditioner airConditioner, int temperature) {
this.airConditioner = airConditioner;
this.newTemperature = temperature;
this.previousTemperature = airConditioner.getTemperature();
}
@Override
public void execute() {
previousTemperature = airConditioner.getTemperature();
airConditioner.setTemperature(newTemperature);
}
@Override
public void undo() {
airConditioner.setTemperature(previousTemperature);
}
}
创建调用者类:
// 调用者 - 遥控器
public class RemoteControl {
private Command command;
private Command lastCommand;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
lastCommand = command;
}
public void pressUndo() {
if (lastCommand != null) {
lastCommand.undo();
lastCommand = null;
}
}
}
最后,客户端使用:
public class SmartHomeClient {
public static void main(String[] args) {
// 创建接收者
Light livingRoomLight = new Light("客厅");
AirConditioner bedroomAC = new AirConditioner();
// 创建命令
Command lightOn = new LightOnCommand(livingRoomLight);
Command lightOff = new LightOffCommand(livingRoomLight);
Command setTemp = new SetTemperatureCommand(bedroomAC, 22);
// 创建调用者
RemoteControl remote = new RemoteControl();
// 执行命令
remote.setCommand(lightOn);
remote.pressButton(); // 打开客厅灯光
remote.setCommand(setTemp);
remote.pressButton(); // 设置空调温度
remote.setCommand(lightOff);
remote.pressButton(); // 关闭客厅灯光
remote.pressUndo(); // 撤销上一个命令(重新打开灯光)
}
}
高级功能实现
命令模式真正强大的地方在于它支持复杂的功能,如命令队列、宏命令等。
命令队列和日志
// 支持命令队列和日志的调用者
public class AdvancedRemoteControl {
private List<Command> commandQueue = new ArrayList<>();
private List<Command> commandHistory = new ArrayList<>();
private int currentHistoryIndex = -1;
public void addToQueue(Command command) {
commandQueue.add(command);
}
public void executeQueue() {
for (Command command : commandQueue) {
command.execute();
commandHistory.add(command);
currentHistoryIndex++;
}
commandQueue.clear();
}
public void undoLast() {
if (currentHistoryIndex >= 0) {
Command command = commandHistory.get(currentHistoryIndex);
command.undo();
currentHistoryIndex--;
}
}
public void redo() {
if (currentHistoryIndex < commandHistory.size() - 1) {
currentHistoryIndex++;
Command command = commandHistory.get(currentHistoryIndex);
command.execute();
}
}
}
宏命令
// 宏命令 - 一次性执行多个命令
public class MacroCommand implements Command {
private List<Command> commands;
public MacroCommand(List<Command> commands) {
this.commands = commands;
}
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
@Override
public void undo() {
// 逆序执行撤销操作
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
// 使用宏命令
public class MacroCommandDemo {
public static void main(String[] args) {
Light livingRoomLight = new Light("客厅");
Light bedroomLight = new Light("卧室");
AirConditioner ac = new AirConditioner();
Command lightOn1 = new LightOnCommand(livingRoomLight);
Command lightOn2 = new LightOnCommand(bedroomLight);
Command setTemp = new SetTemperatureCommand(ac, 22);
List<Command> eveningCommands = Arrays.asList(lightOn1, lightOn2, setTemp);
MacroCommand eveningMode = new MacroCommand(eveningCommands);
RemoteControl remote = new RemoteControl();
remote.setCommand(eveningMode);
remote.pressButton(); // 一键执行所有命令
}
}
实际应用场景
命令模式在现实世界中有广泛的应用:
1. GUI 按钮和菜单操作
在图形用户界面中,按钮点击、菜单选择等操作都可以使用命令模式实现:
// GUI 应用示例
public class GUIApplication {
public static void main(String[] args) {
JFrame frame = new JFrame("命令模式示例");
JButton button = new JButton("执行命令");
// 创建命令
Document document = new Document();
Command saveCommand = new SaveCommand(document);
Command printCommand = new PrintCommand(document);
// 按钮绑定命令
button.addActionListener(e -> saveCommand.execute());
frame.add(button);
frame.pack();
frame.setVisible(true);
}
}
class Document {
public void save() {
System.out.println("文档已保存");
}
public void print() {
System.out.println("文档已打印");
}
}
class SaveCommand implements Command {
private Document document;
public SaveCommand(Document document) {
this.document = document;
}
@Override
public void execute() {
document.save();
}
@Override
public void undo() {
System.out.println("无法撤销保存操作");
}
}
2. 事务系统
在数据库事务中,命令模式可以用于实现事务的原子性:
// 数据库事务命令
public interface TransactionCommand extends Command {
boolean canExecute();
}
public class TransferCommand implements TransactionCommand {
private Account fromAccount;
private Account toAccount;
private BigDecimal amount;
private boolean executed = false;
public TransferCommand(Account from, Account to, BigDecimal amount) {
this.fromAccount = from;
this.toAccount = to;
this.amount = amount;
}
@Override
public boolean canExecute() {
return fromAccount.getBalance().compareTo(amount) >= 0;
}
@Override
public void execute() {
if (canExecute()) {
fromAccount.withdraw(amount);
toAccount.deposit(amount);
executed = true;
System.out.println("转账成功:" + amount);
}
}
@Override
public void undo() {
if (executed) {
toAccount.withdraw(amount);
fromAccount.deposit(amount);
System.out.println("撤销转账:" + amount);
}
}
}
3. 任务调度系统
// 任务调度器
public class TaskScheduler {
private PriorityQueue<ScheduledTask> taskQueue = new PriorityQueue<>();
public void scheduleTask(Command task, long delay) {
ScheduledTask scheduledTask = new ScheduledTask(task,
System.currentTimeMillis() + delay);
taskQueue.offer(scheduledTask);
}
public void processTasks() {
long currentTime = System.currentTimeMillis();
while (!taskQueue.isEmpty() &&
taskQueue.peek().getExecuteTime() <= currentTime) {
ScheduledTask task = taskQueue.poll();
task.getCommand().execute();
}
}
private static class ScheduledTask implements Comparable<ScheduledTask> {
private Command command;
private long executeTime;
public ScheduledTask(Command command, long executeTime) {
this.command = command;
this.executeTime = executeTime;
}
@Override
public int compareTo(ScheduledTask other) {
return Long.compare(this.executeTime, other.executeTime);
}
public Command getCommand() { return command; }
public long getExecuteTime() { return executeTime; }
}
}
命令模式的优缺点
优点
- 解耦调用者和接收者:调用者不需要知道接收者的具体实现细节
- 支持撤销和重做:可以轻松实现命令的撤销和重做功能
- 支持命令队列:可以将命令放入队列中,延迟执行或批量执行
- 易于扩展:新增命令不会影响现有代码
- 支持宏命令:可以组合多个命令形成复杂的操作
缺点
- 可能产生大量具体命令类:每个操作都需要一个具体的命令类
- 增加了系统复杂性:引入了多个新的类和接口
最佳实践
- 使用Lambda表达式简化:在Java 8+中,可以使用函数式接口简化命令模式的实现
- 考虑命令的生命周期:对于需要长时间运行的命令,考虑实现进度跟踪和取消功能
- 命令序列化:如果需要持久化命令,确保命令对象是可序列化的
- 错误处理:在命令执行过程中妥善处理异常情况
// 使用Lambda简化命令模式
public class LambdaCommandDemo {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
// 使用Lambda表达式创建命令
remote.setCommand(() -> System.out.println("简单命令执行"));
remote.pressButton();
// 使用方法引用
Light light = new Light("书房");
remote.setCommand(light::on);
remote.pressButton();
}
}
总结
命令模式通过将请求封装为对象,实现了调用者与接收者的解耦,为软件设计带来了极大的灵活性。它不仅支持基本的命令执行,还能够轻松实现撤销、重做、命令队列、宏命令等高级功能。在实际开发中,合理运用命令模式可以显著提高代码的可维护性和扩展性。
虽然命令模式可能会增加一些类的数量,但其带来的设计优势在复杂的业务场景中往往是值得的。特别是在需要支持操作历史、事务处理、任务调度等功能的系统中,命令模式提供了一个优雅而强大的解决方案。