命令模式
命令模式将"请求"封装成(命令)对象,以便使用不同的请求、队列或者日志来参数化其他对象。 命令模式也支持撤销的操作。
命令模式是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。
设计原则 #
- 解耦:命令模式使发起者和接收者解耦。发起者不关心具体的接收者,只需要根据已知的命令对象
执行
execute()
方法即可。
UML简图 #
classDiagram direction LR class Invoker { +Command command +invoke() } Invoker *..> Command class Command { << interface >> +execute() +undo() } ConcreteCommand ..|> Command class ConcreteCommand { Receiver receiver +execute() +undo() } Receiver <--* ConcreteCommand class Receiver { +someOperation() }
要点 #
- 命令模式将发出请求的对象(调用者)和接收请求的对象(接收者)解耦。
- 被解耦的对象之间通过命令对象沟通,命令对象封装了接收者和一个或者一组动作。
- 调用者通过执行命令对象的
execute()
方法发出请求,这会使得接收者的动作被调用。 - 调用者接收命令作为参数。甚至可以在运行时动态地进行。
- 命令支持撤销。
- 宏命令是命令的简单延伸,允许一次调用多个命令。宏命令也支持撤销。
- 命令也可以用来实现日志和事务系统。How to?
示例代码 #
在本次示例中,假如你有一个家庭影院,你想通过一个“开关”来一键开启家庭影院,按下这个开关后,系统会执行打开空调,调暗灯光,打开CD播放影片等等一系列操作,我们使用命令模式来完成对应的操作。
命令接口 #
首先,我们需要一个命令接口,它定义了基础execute
方法,它并不关心由谁去执行操作。可以想到,操作肯定是由空调,灯光,CD机等去完成操作。总之,我们先定义这个接口。
1public interface CinemaCommand {
2
3 void execute();
4
5 void undo();
6}
7
8public class FamilyCinemaOnCommand implements CinemaCommand {
9
10 private AirConditioner ac;
11 private Light light;
12 private DV dv;
13 private Popcorn popcorn;
14 private Screen screen;
15 private Stereo stereo;
16
17 public FamilyCinemaOnCommand(
18 AirConditioner ac,
19 Light light,
20 DV dv,
21 Popcorn popcorn,
22 Screen screen,
23 Stereo stereo) {
24 this.ac = ac;
25 this.light = light;
26 this.dv = dv;
27 this.popcorn = popcorn;
28 this.screen = screen;
29 this.stereo = stereo;
30 }
31
32 @Override
33 public void execute() {
34 ac.on();
35 popcorn.popcornOn();
36 stereo.stereoOn();
37 screen.screenDown();
38 dv.play("Schindler's List");
39 light.off();
40 }
41
42 @Override
43 public void undo() {
44 light.on();
45 dv.off();
46 screen.screenUp();
47 stereo.stereoOff();
48 popcorn.popcornOff();
49 ac.off();
50 }
51}
命令的接收者 #
FamilyCinemaOnCommand
实现了命令的操作,但是这些操作还没有实际执行,只是当作“命令”发出去了,还需要具体实现来执行操作。这些具体的实现就是命令的接收者。
1public class AirConditioner {
2
3 public void on(){
4 System.out.println("AC on.");
5 }
6
7 public void off(){
8 System.out.println("AC off.");
9 }
10}
11
12public class DV {
13
14 private void setDV(String dv){
15 System.out.println("Ready to play " + dv + ".");
16 }
17
18 private void on(){
19 System.out.println("DV on");
20 }
21
22 public void off(){
23 System.out.println("DV off.");
24 }
25
26 public void play(String dv){
27 on();
28 setDV(dv);
29 }
30}
31
32public class Light {
33
34 public void on() {
35 System.out.println("Light on.");
36 }
37
38 public void off() {
39 System.out.println("Light off.");
40 }
41}
42
43public class Popcorn {
44 public void popcornOn(){
45 System.out.println("Popcorn ready.");
46 }
47
48 public void popcornOff(){
49 System.out.println("Popcorn off.");
50 }
51}
52
53public class Screen {
54 public void screenDown(){
55 System.out.println("Screen down.");
56 }
57
58 public void screenUp(){
59 System.out.println("Screen off.");
60 }
61}
62
63public class Stereo {
64 public void stereoOn(){
65 System.out.println("Stereo on.");
66 }
67
68 public void stereoOff(){
69 System.out.println("Stereo off.");
70 }
71}
命令执行器 #
我们并不通过命令接口直接执行命令,我们需要一个执行器。执行器需要一个“命令对象”来实例化,这个命令对象包含了需要执行的操作。
1public class RemoteControl {
2
3 private CinemaCommand cinemaCommand;
4
5 public RemoteControl(CinemaCommand cinemaCommand) {
6 this.cinemaCommand = cinemaCommand;
7 }
8
9 public void onButtonPress(){
10 cinemaCommand.execute();
11 }
12
13 public void redoCommand(){
14 cinemaCommand.undo();
15 }
16}
测试代码 #
1public class TestCommand {
2 public static void main(String[] args) {
3// simpleCommand();
4 batchCommand();
5 }
6
7 static void batchCommand(){
8 AirConditioner ac = new AirConditioner();
9 Light light = new Light();
10 Popcorn popcorn = new Popcorn();
11 Stereo stereo= new Stereo();
12 DV dv = new DV();
13 Screen screen = new Screen();
14 FamilyCinemaOnCommand familyCinemaOnCommand
15 = new FamilyCinemaOnCommand(ac,light, dv, popcorn, screen,stereo);
16
17 RemoteControl remoteControl = new RemoteControl(familyCinemaOnCommand);
18 remoteControl.onButtonPress();
19 System.out.println("---------------");
20 remoteControl.redoCommand();
21 }
22}
23
24///:~
25//AC on.
26//Popcorn ready.
27//Stereo on.
28//Screen down.
29//DV on
30//Ready to play Schindler's List.
31//Light off.
32//---------------
33//Light on.
34//DV off.
35//Screen off.
36//Stereo off.
37//Popcorn off.
38//AC off.
39//
小结 #
Generated by gemini, revised.
命令模式是一种行为型设计模式,它将一个请求或者操作封装成一个对象。这允许你将请求排队或记录请求日志,以及支持可撤销的操作。简单来说,命令模式将“发出请求”的对象(调用者)和“执行请求”的对象(接收者)解耦。
核心思想:
封装请求: 将一个操作及其参数封装成一个命令对象。
解耦调用者和接收者: 调用者不直接知道谁来执行请求,只需要知道如何发出命令。
支持撤销和重做: 由于命令被封装成对象,可以轻松地实现撤销和重做功能。
支持队列请求: 可以将多个命令放入队列中,按顺序执行。
命令模式中的角色:
Command(命令): 声明执行操作的接口。
ConcreteCommand(具体命令): 实现命令接口,并关联一个接收者对象。它定义了调用接收者执行操作的具体实现。
Receiver(接收者): 知道如何执行与请求相关的操作。实际上执行操作的对象。
Invoker(调用者): 持有一个命令对象,并请求命令执行。调用者不了解具体命令的实现细节。
Client(客户端): 创建具体命令对象,并设置其接收者。
命令模式的优点:
解耦: 将调用者和接收者解耦,提高了系统的灵活性和可维护性。
可扩展性: 易于添加新的命令,无需修改现有代码。
可撤销: 支持撤销和重做操作。
可排队: 支持将多个命令放入队列中,按顺序执行。
易于实现日志记录: 可以将命令对象保存到日志中,以便追踪操作历史。
命令模式的缺点:
可能导致类数量增加: 对于每个操作都需要创建一个具体的命令类,这可能导致类的数量增加,使系统更加复杂。
增加系统复杂度: 引入了额外的类和接口,可能增加系统的复杂性。
命令模式的适用场景:
需要将请求的发送者和接收者解耦的场景。
需要支持撤销和重做操作的场景。
需要将多个请求排队或记录请求日志的场景。
需要支持宏命令(组合多个命令)的场景。
GUI 应用程序中的菜单、按钮等操作。
事务处理系统。
与其他模式的关系:
策略模式: 命令模式和策略模式都封装了算法或行为。不同之处在于,策略模式通常用于选择不同的算法,而命令模式则用于封装请求。
备忘录模式: 命令模式可以使用备忘录模式来保存命令执行前的状态,以便实现撤销操作。
总之,命令模式是一种强大的设计模式,它可以将请求封装成对象,从而实现调用者和接收者的解耦、支持撤销和重做操作、以及支持队列请求等功能。虽然它可能会增加系统的复杂性,但在合适的场景下,它可以提高系统的灵活性和可维护性。 理解其核心思想、参与者、优缺点以及适用场景,可以帮助你更好地应用命令模式解决实际问题。