概念和作用
命令模式将请求封装为对象,使请求的发送者和接收者解耦。通过参数化请求、支持队列、日志、撤销等功能,增强系统的灵活性和扩展性。
场景
1.需要解耦请求发送者和接收者。
2.需要支持命令的排队、撤销/重做、日志记录。
3.需要动态指定请求或组合多个请求。
示例
// 1.命令接口
interface Command {
void execute();
void undo(); // 可选撤销功能
}
// 2.接收者(真正执行操作的对象)
class PhoneInventory {
private int stock = 10;
public void buyPhones(int quantity) {
stock += quantity;
System.out.println("采购成功,当前库存:" + stock);
}
public void sellPhones(int quantity) {
if(stock >= quantity) {
stock -= quantity;
System.out.println("销售成功,当前库存:" + stock);
} else {
System.out.println("库存不足,销售失败");
}
}
}
// 3.具体命令:购买
class BuyCommand implements Command {
private PhoneInventory inventory;
private int quantity;
public BuyCommand(PhoneInventory inventory, int quantity) {
this.inventory = inventory;
this.quantity = quantity;
}
@Override
public void execute() {
inventory.buyPhones(quantity);
}
@Override
public void undo() {
inventory.sellPhones(quantity);
}
}
// 3.具体命令:销售
class SellCommand implements Command {
private PhoneInventory inventory;
private int quantity;
public SellCommand(PhoneInventory inventory, int quantity) {
this.inventory = inventory;
this.quantity = quantity;
}
@Override
public void execute() {
inventory.sellPhones(quantity);
}
@Override
public void undo() {
inventory.buyPhones(quantity);
}
}
// 4.调用者(触发命令的对象)
class OrderProcessor {
private List<Command> commands = new ArrayList<>();
// 添加命令
public void addCommand(Command command) {
commands.add(command);
}
// 执行命令
public void processOrders() {
for(Command cmd : commands) {
cmd.execute();
}
commands.clear();
}
}
// 5.客户端使用
public class Main {
public static void main(String[] args) {
// 接收者
PhoneInventory inventory = new PhoneInventory();
// 调用者
OrderProcessor processor = new OrderProcessor();
// 创建命令对象
processor.addCommand(new BuyCommand(inventory, 5));
processor.addCommand(new SellCommand(inventory, 3));
// 统一执行命令
processor.processOrders();
}
}
优点和缺点
优点
**1.解耦:**调用者与具体操作解耦,调用者不需要知道具体实现。
**2.可扩展:**容易添加新命令,符合开闭原则。
**3.组合命令:**支持宏命令(组合多个命令)。
**4.支持撤销:**通过添加undo方法实现操作回滚。
缺点
**1.类膨胀:**每个命令都需要一个具体类。
**2.复杂度增加:**需要多层级对象协作。
不用命令模式的实现
// 直接调用库存方法
public class WithoutCommandPattern {
public static void main(String[] args) {
PhoneInventory inventory = new PhoneInventory();
// 直接调用具体方法
inventory.buyPhones(5);
inventory.sellPhones(3);
}
}
缺陷
1.紧耦合:调用者直接依赖具体实现。
// 不使用命令模式时
public class WithoutCommandPattern {
public static void main(String[] args) {
PhoneInventory inventory = new PhoneInventory();
// 直接调用具体方法
inventory.buyPhones(5); // 直接依赖 buyPhones 方法
inventory.sellPhones(3); // 直接依赖 sellPhones 方法
}
}
问题说明:
调用者(客户端 main
方法)直接调用 PhoneInventory
的具体方法 buyPhones
和 sellPhones
。如果 PhoneInventory
的方法名或参数改变(如 buyPhones
改名为 purchasePhones
),所有调用它的代码都需要修改。
2.难以扩展:新增功能需要修改现有代码。
假设需要新增一个 退货功能 returnPhones(int quantity)
,此时:
// 非命令模式需要修改调用方
public class WithoutCommandPattern {
public static void main(String[] args) {
PhoneInventory inventory = new PhoneInventory();
inventory.buyPhones(5);
inventory.sellPhones(3);
// 新增退货功能
inventory.returnPhones(2); // 必须修改客户端代码
}
}
问题说明:
调用者必须知道新方法 returnPhones
的存在并直接调用它。
违反开闭原则:扩展功能需要修改现有客户端代码。
对比命令模式:
命令模式只需新增 ReturnCommand
类,客户端通过添加命令对象即可扩展功能,无需修改现有调用逻辑。
3.无法支持撤销:需要额外维护操作历史。
在非命令模式中,若用户想要撤销一次销售操作:
public class WithoutCommandPattern {
public static void main(String[] args) {
PhoneInventory inventory = new PhoneInventory();
inventory.buyPhones(5); // 初始库存 15
inventory.sellPhones(3); // 库存 12
// 撤销销售操作(需要手动反向操作)
inventory.buyPhones(3); // 必须自行记录操作历史并反向调用
}
}
问题说明:
调用者需自行记录每次操作的类型和参数(如销售了 3 台手机),并手动调用反向操作(如再采购 3 台)。
若操作历史复杂(如涉及多个对象),维护成本极高。
对比命令模式:
命令模式通过 Command
接口的 undo()
方法,自动记录操作历史并实现撤销。
4.缺乏灵活性:无法实现命令队列、延迟执行等高级功能。
假设需要将多个操作批量执行或延迟到特定时间执行:
public class WithoutCommandPattern {
public static void main(String[] args) {
PhoneInventory inventory = new PhoneInventory();
// 直接调用方法,无法延迟执行或批量管理
inventory.buyPhones(5);
inventory.sellPhones(3);
// 若想实现延迟执行,需自行设计线程或定时任务
}
}
问题说明:
调用者直接触发方法调用,所有操作立即执行。
若想实现命令队列、延迟执行或事务管理(如批量操作全部成功或全部失败),需额外开发复杂的逻辑。
对比命令模式:
命令模式通过 OrderProcessor
类管理命令队列,可轻松实现批量执行、延迟执行或事务回滚:
// 命令模式支持延迟执行
processor.addCommand(new BuyCommand(inventory, 5));
processor.addCommand(new SellCommand(inventory, 3));
// 在需要时统一执行
processor.processOrders();
总结对比
|------|---------------------------|---------------------|
| 特性 | 非命令模式实现 | 命令模式实现 |
| 耦合性 | 调用者直接依赖 PhoneInventory 方法 | 调用者依赖抽象的 Command 接口 |
| 扩展性 | 需修改调用方代码 | 通过新增 Command 子类扩展 |
| 撤销支持 | 需自行维护操作历史 | 内置 undo() 方法实现撤销 |
| 灵活性 | 无法实现队列、延迟执行等高级功能 | 通过命令队列支持批量处理、延迟执行 |
实际意义
命令模式通过 封装请求为对象,将操作的具体实现与调用者解耦,从而解决了上述问题。对于需要支持复杂操作管理(如撤销、重做、事务)的场景,命令模式是更优的设计选择。而对于简单的一次性操作,直接调用可能更高效。