设计模式(18):命令模式COMMAND

命令模式

意图

将一个请求封装为一个对象,从而使你可以在接收不同的请求、请求队列或者日志请求时对客户进行参数化,并且支持可撤消的操作。


使用场景

当你有如下需求时,可使用Command模式:

  • 可以抽象出待执行的动作以参数化某对象。

    你可用过程语言中的回调(callback)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。
  • 支持取消操作。
  • Command模式提供了对事务进行建模的方法。

结构

命令模式结构如下
ShowImage

  • Command

    – 声明执行操作的接口。

  • ConcreteCommand

    – 将一个Receiver对象与一个Action绑定。

    – 调用接收者相应的操作,以实现Execute。

  • Client

    – 创建一个Concrete Command对象并设定它的Receiver。

  • Invoker

    – 要求该命令执行这个请求。

  • Receiver

    – 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个Receiver。

协作

  1. Client创建一个ConcreteCommand对象并指定它的Receiver对象。
  2. 某Invoker对象存储该ConcreteCommand对象。
  3. 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤消的,ConcreteCommand就在执行Excute操作之前存储当前状态以用于取消该命令。
  4. ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求。

时序图如下所示
ShowImage


效果

  • Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
  • Command是顶层的对象。它们可像其他的对象一样被操纵和扩展。
  • 你可将多个命令装配成一个复合命令
  • 扩展性良好,增加新的Command很容易,因为这无需改变已有的类。

命令模式实现(Implement)

案例

饮品店新开,有三种供客人选择,分别是奶茶、咖啡和果汁,现在用命令模式将这三种点单行为封装成对象。

代码实现

点单Order类,对应Command

1
2
3
4
/* Create a Command Interface */
public interface Order {
void execute();
}

柜台类BeverageCounter,对应Receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Receiver class in pattern
* Beverage Counter
*/
public class BeverageCounter {
//real order coffee action
public void orderCoffee() {
System.out.println("点了一杯咖啡......咖啡已经就绪...");
}
//real order juice action
public void orderJuice() {
System.out.println("点了一杯果汁......果汁已经就绪...");
}
//real order milk tea action
public void orderMilkTea() {
System.out.println("点了一杯奶茶......奶茶已经就绪...");
}
}

BeverageShopBroker类,对应Invoker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Invoker class in pattern
* Beverage Shop Broker
*/
public class BeverageShopBroker {
private List<Order> ordersList = new ArrayList<>();
//下单
public void takeOrder(Order order) {
ordersList.add(order);
}
//取消订单
public void cancelOrder(Order order) {
Stream<Order> stream = ordersList.stream();
ordersList.remove(stream.filter(t -> t.equals(order)).findFirst().get());
}
//制作
public void placeOrders() {
for (Order order : ordersList) {
order.execute();
}
ordersList.clear();
}
}

具体命令类,这里只展示BuyCoffee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Create a ConcreteCommand Class
* buy coffee
*/
public class BuyCoffee implements Order {
private BeverageCounter beverageCounter;
public BuyCoffee(BeverageCounter beverageCounter) {
this.beverageCounter = beverageCounter;
}
@Override
public void execute() {
beverageCounter.orderCoffee();
}
}

Client类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* Client class in pattern*/
public class CommandDemoTest {
@Test
public void beverageOrderTest() {
BeverageCounter beverageCounter = new BeverageCounter();
//创建三种下单命令
BuyCoffee buyCoffee = new BuyCoffee(beverageCounter);
BuyJuice buyJuice = new BuyJuice(beverageCounter);
BuyMilkTea buyMilkTea = new BuyMilkTea(beverageCounter);
BeverageShopBroker beverageShopBroker = new BeverageShopBroker();
//饮品店来了5个客户,点了1杯果汁,3杯咖啡,一杯奶茶
beverageShopBroker.takeOrder(buyJuice);
beverageShopBroker.takeOrder(buyCoffee);
beverageShopBroker.takeOrder(buyCoffee);
beverageShopBroker.takeOrder(buyCoffee);
beverageShopBroker.takeOrder(buyMilkTea);
//其中两个客户突然不想要咖啡了,于是撤销之前的订单
beverageShopBroker.cancelOrder(buyCoffee);
beverageShopBroker.cancelOrder(buyCoffee);
//两个客户换成了果汁和奶茶
beverageShopBroker.takeOrder(buyJuice);
beverageShopBroker.takeOrder(buyMilkTea);
//根据订单开始制作饮品
beverageShopBroker.placeOrders();
}
}

这里的撤销命令并没有使用命令模式,只是将列表第一条匹配的订单对象撤销,如果想要使用撤销,必须在Command抽象中定义unexecute方法来取消执行,并且ConcreteCommand类中还需要储存额外的状态信息:

  • Receiver对象,它真正执行处理该请求的各操作。
  • Receiver上执行操作的参数。
  • 如果处理请求的操作会改变Receiver对象中的某些值,那么这些值也必须先存储起来。Receiver还必须提供一些操作,以使该命令可将Receiver恢复到它先前的状态。

总结

命令模式的核心在于将请求或者动作(action)转换为事务,用对象来表示行为,使得行为可以重复操作或者取消。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!