什么是命令模式?
命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

核心思想: 将“发出请求的对象”(调用者/Invoker)与“接收并执行请求的对象”(接收者/Receiver)解耦,调用者不需要知道接收者是谁,也不需要知道具体如何执行请求,它只需要调用一个命令对象的方法即可。
命令模式的四大核心角色
为了更好地理解,我们用一个生活中的例子点外卖来类比。
| 角色 | 类比(点外卖) | 在设计模式中的定义 |
|---|---|---|
| Command (命令接口) | “订单”的规范 | 定义了执行操作的接口,通常是 execute() 方法。 |
| ConcreteCommand (具体命令) | 一份具体的“川菜订单”或“粤菜订单” | 实现了 Command 接口,持有对接收者对象的引用,并调用接收者的相应操作来执行请求。 |
| Invoker (调用者/请求者) | “顾客”或“手机App” | 要求命令对象执行请求,它会调用命令对象的 execute() 方法,但它本身并不知道命令的具体内容或接收者是谁。 |
| Receiver (接收者) | “川菜馆”或“粤菜馆” | 知道如何执行与请求相关的具体业务逻辑,是真正干活的对象。 |
还有一个非常重要的角色: | Client (客户端) | 创建订单的人 | 创建具体命令对象,并设定它的接收者。 |
代码实现(点外卖例子)
下面我们用 Java 代码来实现这个点外卖的例子。

第1步:定义 Receiver (接收者) - 餐厅
这是真正执行做饭动作的对象。
// Receiver.java - 接收者
public class Restaurant {
private String dishName;
public Restaurant(String dishName) {
this.dishName = dishName;
}
// 真正执行做饭的动作
public void cook() {
System.out.println(dishName + " 餐厅正在为您精心烹饪...");
}
}
第2步:定义 Command (命令接口)
// Command.java - 命令接口
public interface Order {
// 执行命令的方法
void execute();
}
第3步:定义 ConcreteCommand (具体命令)
这个命令对象会持有一个接收者(餐厅)的引用,并调用它的 cook() 方法。
// SichuanDishOrder.java - 具体命令
public class SichuanDishOrder implements Order {
private Restaurant restaurant; // 持有接收者的引用
public SichuanDishOrder(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void execute() {
restaurant.cook(); // 调用接收者的方法来执行具体操作
}
}
第4步:定义 Invoker (调用者) - 顾客/App
顾客不关心是哪家餐厅做菜,他只知道下一个订单。
// Customer.java - 调用者
public class Customer {
private Order order; // 持有一个命令对象的引用
// 设置要下的订单
public void setOrder(Order order) {
this.order = order;
}
// 下单,执行订单
public void placeOrder() {
if (order != null) {
order.execute();
} else {
System.out.println("您还没有选择菜品!");
}
}
}
第5步:定义 Client (客户端) - 创建并组装对象
// Client.java - 客户端
public class DeliveryApp {
public static void main(String[] args) {
// 1. 创建接收者 (餐厅)
Restaurant sichuanRestaurant = new Restaurant("麻婆豆腐");
Restaurant cantoneseRestaurant = new Restaurant("白切鸡");
// 2. 创建具体命令对象,并设定它的接收者
Order sichuanOrder = new SichuanDishOrder(sichuanRestaurant);
Order cantoneseOrder = new SichuanDishOrder(cantoneseRestaurant); // 注意:这里也可以用另一个具体命令类
// 3. 创建调用者 (顾客)
Customer customer = new Customer();
// 4. 客户端将命令对象传递给调用者
customer.setOrder(sichuanOrder);
customer.placeOrder(); // 输出: 麻婆豆腐 餐厅正在为您精心烹饪...
System.out.println("--------------------");
// 5. 可以动态地更换命令
customer.setOrder(cantoneseOrder);
customer.placeOrder(); // 输出: 白切鸡 餐厅正在为您精心烹饪...
}
}
运行结果
麻婆豆腐 餐厅正在为您精心烹饪...
--------------------
白切鸡 餐厅正在为您精心烹饪...
通过这个例子,你可以清晰地看到:

Customer(Invoker) 完全不知道Restaurant(Receiver) 的存在。Customer只和Order(Command) 打交道,它调用order.execute()。SichuanDishOrder(ConcreteCommand) 连接了Customer和Restaurant,它知道如何让Restaurant做饭。- 这就是解耦的核心体现。
命令模式的优点和缺点
优点
- 解耦:这是命令模式最大的优点,调用者和接收者完全解耦,使得调用者无需知道接收者的任何信息。
- 可扩展性强:可以很容易地引入新的命令,而无需修改调用者或现有接收者的代码。
- 支持组合命令:可以将多个命令组合成一个宏命令(Composite Command),一次性执行一系列操作。
- 支持撤销操作:通过在
Command接口中增加undo()方法,并在具体命令中实现它,可以轻松实现撤销功能。 - 支持请求排队和日志:可以将命令对象放入队列中,实现异步执行,也可以将命令对象持久化,实现请求日志。
缺点
- 可能导致类数量过多:对于每一个具体的操作,都需要创建一个具体的命令类,如果系统中有大量操作,可能会导致类的数量急剧增加。
- 增加系统的复杂性:引入了新的抽象层(Command接口),对于非常简单的场景,可能会显得小题大做。
命令模式的应用场景
命令模式在以下场景中非常有用:
- GUI按钮和菜单项:每个按钮或菜单项都是一个命令,点击按钮就是调用命令的
execute()方法,这使得你可以轻松地改变按钮的行为,只需给它一个新的命令对象即可。 - 事务系统:在数据库操作中,可以将一系列操作封装成命令,如果执行成功,则提交;如果失败,则可以调用
undo()方法回滚。 - 多线程任务队列:生产者将任务(命令对象)放入队列,消费者从队列中取出任务并执行。
- 撤销/重做 功能:几乎所有支持撤销功能的软件(如Photoshop, Word)都使用了命令模式,每次操作都会创建一个命令对象,并将其放入历史记录栈中,撤销时,只需从栈中弹出上一个命令并调用其
undo()方法即可。
增强版:支持撤销的命令模式
我们稍微修改一下 Order 接口和 SichuanDishOrder 来支持撤销。
// 增强的命令接口
public interface Order {
void execute();
void undo(); // 新增撤销方法
}
// 增强的具体命令
public class SichuanDishOrder implements Order {
private Restaurant restaurant;
public SichuanDishOrder(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void execute() {
restaurant.cook();
}
@Override
public void undo() {
System.out.println(restaurant.getDishName() + " 订单已取消,餐厅停止烹饪。");
}
}
// 在Customer中增加撤销方法
public class Customer {
// ... 其他代码不变
private Order lastOrder; // 记录上一个订单
public void placeOrder() {
if (order != null) {
lastOrder = order; // 记录当前订单为上一个订单
order.execute();
} else {
System.out.println("您还没有选择菜品!");
}
}
public void cancelLastOrder() {
if (lastOrder != null) {
lastOrder.undo();
lastOrder = null;
} else {
System.out.println("没有可撤销的订单。");
}
}
}
// 在main方法中测试
public static void main(String[] args) {
Restaurant restaurant = new Restaurant("水煮鱼");
Order order = new SichuanDishOrder(restaurant);
Customer customer = new Customer();
customer.setOrder(order);
customer.placeOrder(); // 输出: 水煮鱼 餐厅正在为您精心烹饪...
System.out.println("--------------------");
customer.cancelLastOrder(); // 输出: 水煮鱼 订单已取消,餐厅停止烹饪。
}
与其他模式的比较
-
命令模式 vs 策略模式:
- 相似点:两者都将算法/行为封装成对象。
- 不同点:策略模式关注的是封装可互换的算法,使得算法可以独立于使用它的客户端而变化,命令模式关注的是封装一个请求,以便将其排队、记录日志或支持撤销,策略模式的
Context通常会直接调用Strategy的方法,而命令模式的Invoker和Receiver是完全解耦的。
-
命令模式 vs 适配器模式:
- 两者都包装一个对象,但适配器模式是为了接口不兼容的两个对象之间建立桥梁,而命令模式是为了将请求的发送者和接收者解耦。
命令模式是一个非常强大的模式,它的核心价值在于解耦和封装请求,虽然可能会增加一些类的数量,但它带来的灵活性、可扩展性和对高级功能(如撤销、宏命令)的支持,使其在构建复杂、灵活的系统时成为一个不可或缺的工具,在Java中,java.lang.Runnable 接口就可以看作是一个命令模式的应用,run() 方法就是 execute() 方法。
