命令模式

命令模式

命令模式将"请求"封装成(命令)对象,以便使用不同的请求、队列或者日志来参数化其他对象。 命令模式也支持撤销的操作。

命令模式是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。

设计原则 #

  1. 解耦:命令模式使发起者和接收者解耦。发起者不关心具体的接收者,只需要根据已知的命令对象 执行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()
}

要点 #

  1. 命令模式将发出请求的对象(调用者)和接收请求的对象(接收者)解耦。
  2. 被解耦的对象之间通过命令对象沟通,命令对象封装了接收者和一个或者一组动作。
  3. 调用者通过执行命令对象的execute()方法发出请求,这会使得接收者的动作被调用。
  4. 调用者接收命令作为参数。甚至可以在运行时动态地进行。
  5. 命令支持撤销。
  6. 宏命令是命令的简单延伸,允许一次调用多个命令。宏命令也支持撤销。
  7. 命令也可以用来实现日志和事务系统。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.

命令模式是一种行为型设计模式,它将一个请求或者操作封装成一个对象。这允许你将请求排队或记录请求日志,以及支持可撤销的操作。简单来说,命令模式将“发出请求”的对象(调用者)和“执行请求”的对象(接收者)解耦。

  1. 核心思想:

    • 封装请求: 将一个操作及其参数封装成一个命令对象。

    • 解耦调用者和接收者: 调用者不直接知道谁来执行请求,只需要知道如何发出命令。

    • 支持撤销和重做: 由于命令被封装成对象,可以轻松地实现撤销和重做功能。

    • 支持队列请求: 可以将多个命令放入队列中,按顺序执行。

  2. 命令模式中的角色:

    • Command(命令): 声明执行操作的接口。

    • ConcreteCommand(具体命令): 实现命令接口,并关联一个接收者对象。它定义了调用接收者执行操作的具体实现。

    • Receiver(接收者): 知道如何执行与请求相关的操作。实际上执行操作的对象。

    • Invoker(调用者): 持有一个命令对象,并请求命令执行。调用者不了解具体命令的实现细节。

    • Client(客户端): 创建具体命令对象,并设置其接收者。

  3. 命令模式的优点:

    • 解耦: 将调用者和接收者解耦,提高了系统的灵活性和可维护性。

    • 可扩展性: 易于添加新的命令,无需修改现有代码。

    • 可撤销: 支持撤销和重做操作。

    • 可排队: 支持将多个命令放入队列中,按顺序执行。

    • 易于实现日志记录: 可以将命令对象保存到日志中,以便追踪操作历史。

  4. 命令模式的缺点:

    • 可能导致类数量增加: 对于每个操作都需要创建一个具体的命令类,这可能导致类的数量增加,使系统更加复杂。

    • 增加系统复杂度: 引入了额外的类和接口,可能增加系统的复杂性。

  5. 命令模式的适用场景:

    • 需要将请求的发送者和接收者解耦的场景。

    • 需要支持撤销和重做操作的场景。

    • 需要将多个请求排队或记录请求日志的场景。

    • 需要支持宏命令(组合多个命令)的场景。

    • GUI 应用程序中的菜单、按钮等操作。

    • 事务处理系统。

  6. 与其他模式的关系:

    • 策略模式: 命令模式和策略模式都封装了算法或行为。不同之处在于,策略模式通常用于选择不同的算法,而命令模式则用于封装请求。

    • 备忘录模式: 命令模式可以使用备忘录模式来保存命令执行前的状态,以便实现撤销操作。

总之,命令模式是一种强大的设计模式,它可以将请求封装成对象,从而实现调用者和接收者的解耦、支持撤销和重做操作、以及支持队列请求等功能。虽然它可能会增加系统的复杂性,但在合适的场景下,它可以提高系统的灵活性和可维护性。 理解其核心思想、参与者、优缺点以及适用场景,可以帮助你更好地应用命令模式解决实际问题。