命令模式

命令模式

万能遥控板

前方售前团队带来了好消息,拿到了巴斯特家电自动化公司的业务。
需求是这样的,这家公司正在设计一款遥控板,和普通的遥控板不同,它提供了若干按键,可以通过编程控制不同的家电,但这些家电是由不同的厂商开发出来的,比如小米的电视,格力的空调等,他们提供了不同的API。同时,按键可以扩展以便支持未来可能合作的家电。

V1版

我们一步一步来逐步分析实现。先实现第一版,如果要让遥控板控制电视开关,我们怎么处理?
先把电视类实现出来(这个类由厂商提供)

/**
 * 电视
 */
public class TelevisionV1 {
    public void on(){
        System.out.println("打开电视....");
        System.out.println("正在放映<<哪吒>>");
    }

    public void off() {
        System.out.println("关闭电视....");
    }
}

遥控板实现

/**
 * V1版遥控板
 */
public class TVRemoteControlV1 {

    TelevisionV1 television;

    public TVRemoteControlV1() {
        television = new TelevisionV1();
    }

    public void powerOn(){
        System.out.println("按下遥控板的电源打开按钮....");
        television.on();
    }

    public void powerOff(){
        System.out.println("按下遥控板的电源关闭按钮....");
        television.off();
    }
}

测试

/**
 * 测试
 */
public class RemoteControlMainV1 {
    public static void main(String[] args) {
        TVRemoteControlV1 remoteControl = new TVRemoteControlV1();
        remoteControl.powerOn();
        System.out.println();
        remoteControl.powerOff();
    }
}

恩,通过这种简单自然的方式,遥控板类中组合电视,就可以使用遥控板来控制电视了。

V2版

接下来,我们让我们的遥控板既能控制电视,又可以控制空调。按照V1一样的思路,实现V2版

/**
 * 电视-小米
 */
public class TelevisionV2 {
    public void on(){
        System.out.println("打开电视....");
        System.out.println("正在放映<<哪吒>>");
    }

    public void off() {
        System.out.println("关闭电视....");
    }
}

/**
 * 空调-格力
 */
public class AirConditionerV2 {
    public void turnOn(){
        System.out.println("打开空调....");
        System.out.println("吹出26度的舒适冷风....");
    }

    public void turnOff() {
        System.out.println("关闭空调....");
    }
}

V2版本的遥控板实现

/**
 * V2版遥控板
 */
public class RemoteControlV2 {

    TelevisionV2 television;
    AirConditionerV2 airConditioner;

    public RemoteControlV2() {
        television = new TelevisionV2();
        airConditioner = new AirConditionerV2();
    }

    public void tvPowerOn(){
        System.out.println("按下遥控板的电视电源打开按钮....");
        television.on();
    }

    public void tvPowerOff(){
        System.out.println("按下遥控板的电视电源关闭按钮....");
        television.off();
    }

    public void airConditionerPowerOn(){
        System.out.println("按下遥控板的空调电源打开按钮....");
        airConditioner.turnOn();
    }

    public void airConditionerPowerOff(){
        System.out.println("按下遥控板的空调电源关闭按钮....");
        airConditioner.turnOff();
    }
}

测试

/**
 * 测试
 */
public class RemoteControlMainV2 {
    public static void main(String[] args) {
        RemoteControlV2 remoteControl = new RemoteControlV2();
        remoteControl.tvPowerOn();
        System.out.println();
        remoteControl.airConditionerPowerOn();
        System.out.println();
        remoteControl.tvPowerOff();
        System.out.println();
        remoteControl.airConditionerPowerOff();
    }
}

好,V2版遥控板通过组合多个厂商的家电,调用厂商家电类的API,初步实现了同一个遥控板控制不同家电的功能。
不过,分析上面的实现,我们不难发现,遥控板和具体的厂商家电类是紧耦合的,换言之,厂商家电类API发生变化,遥控板要跟着变化;
另外,要新增控制的家电时,也要修改遥控板类。
这里,我们称遥控板为发送请求者,厂商家电类为执行请求者,前两个版本的实现中,发送请求者和执行请求者耦合在一起,有没有一种方法,将它们解耦,这样一来,厂商家电类API发生变化不会影响遥控器类,而增加控制的家电时,也仅仅是通过扩展类的方式来实现?

V3版

带着这种思路,我们在请求者和执行者中间加了一层,称这一层为指令,即遥控器只管发送指令,而不用关心指令是被谁,以什么样的方式执行的。
来看看V3版本的代码
首先厂商家电类,没变化,复用V2的

/**
 * 电视-小米
 */
public class TelevisionV3 {
    public void on(){
        System.out.println("打开电视....");
        System.out.println("正在放映<<哪吒>>");
    }

    public void off() {
        System.out.println("关闭电视....");
    }
}

/**
 * 空调-格力
 */
public class AirConditionerV3 {
    public void turnOn(){
        System.out.println("打开空调....");
        System.out.println("吹出26度的舒适冷风....");
    }

    public void turnOff() {
        System.out.println("关闭空调....");
    }
}

接下来,我们抽象出指令接口

/**
 * 遥控指令
 */
public interface ControlCommandV3 {
    void execute();
}

然后,针对遥控器上的每个按钮操作,实现具体的指令

/**
 * 打开电视指令
 */
public class TVPowerOnCommandV3 implements ControlCommandV3 {

    private TelevisionV3 television;

    public TVPowerOnCommandV3(TelevisionV3 television) {
        this.television = television;
    }

    @Override
    public void execute() {
        System.out.println("执行【打开电视指令】...");
        television.on();
    }
}

/**
 * 关闭电视指令
 */
public class TVPowerOffCommandV3 implements ControlCommandV3 {

    private TelevisionV3 television;

    public TVPowerOffCommandV3(TelevisionV3 television) {
        this.television = television;
    }

    @Override
    public void execute() {
        System.out.println("执行【关闭电视指令】...");
        television.off();
    }
}

/**
 * 打开空调指令
 */
public class AirConditionerPowerOnCommandV3 implements ControlCommandV3 {

    private AirConditionerV3 airConditionerV3;

    public AirConditionerPowerOnCommandV3(AirConditionerV3 airConditionerV3) {
        this.airConditionerV3 = airConditionerV3;
    }

    @Override
    public void execute() {
        System.out.println("执行【打开空调指令】...");
        airConditionerV3.turnOn();
    }
}

/**
 * 关闭空调指令
 */
public class AirConditionerPowerOffCommandV3 implements ControlCommandV3 {

    private AirConditionerV3 airConditionerV3;

    public AirConditionerPowerOffCommandV3(AirConditionerV3 airConditionerV3) {
        this.airConditionerV3 = airConditionerV3;
    }

    @Override
    public void execute() {
        System.out.println("执行【关闭空调指令】...");
        airConditionerV3.turnOff();
    }
}

然后,我们在遥控器中,将这些指令初始化并存储起来

/**
 * V3版遥控板
 */
public class RemoteControlV3 {

    private TVPowerOnCommandV3 tvPowerOnCommand;
    private TVPowerOffCommandV3 tvPowerOffCommand;
    private AirConditionerPowerOnCommandV3 airConditionerPowerOnCommand;
    private AirConditionerPowerOffCommandV3 airConditionerPowerOffCommand;

    public RemoteControlV3() {
        TelevisionV3 televisionV3 = new TelevisionV3();
        tvPowerOnCommand = new TVPowerOnCommandV3(televisionV3);
        tvPowerOffCommand = new TVPowerOffCommandV3(televisionV3);

        AirConditionerV3 airConditionerV3 = new AirConditionerV3();
        airConditionerPowerOnCommand = new AirConditionerPowerOnCommandV3(airConditionerV3);
        airConditionerPowerOffCommand = new AirConditionerPowerOffCommandV3(airConditionerV3);
    }

    public void tvPowerOn(){
        System.out.println("按下遥控板的电视电源打开按钮....");
        tvPowerOnCommand.execute();
    }

    public void tvPowerOff(){
        System.out.println("按下遥控板的电视电源关闭按钮....");
        tvPowerOffCommand.execute();
    }

    public void airConditionerPowerOn(){
        System.out.println("按下遥控板的空调电源打开按钮....");
        airConditionerPowerOnCommand.execute();
    }

    public void airConditionerPowerOff(){
        System.out.println("按下遥控板的空调电源关闭按钮....");
        airConditionerPowerOffCommand.execute();
    }
}

测试

/**
 * 测试
 */
public class RemoteControlMainV3 {
    public static void main(String[] args) {
        RemoteControlV3 remoteControl = new RemoteControlV3();
        remoteControl.tvPowerOn();
        System.out.println();
        remoteControl.airConditionerPowerOn();
        System.out.println();
        remoteControl.tvPowerOff();
        System.out.println();
        remoteControl.airConditionerPowerOff();
    }
}

在这一版中,通过指令类解耦了遥控板类和厂商类的调用,当厂商类的API发生变更时,只需要修改对应的指令类,而不用修改遥控器类了。
不过,这一版遥控器和具体厂商类还是有耦合,那么新加入控制的厂商类时,还是要修改。
原来我们讲过,要分离和封装变化,V3班的遥控板类中,容易扩展和变化的就是存储的指令类了,那么有什么方法解决。

V4版

我们来看V4版的实现,厂商类同前一样。忽略
指令类和V3版没有本质区别,添加了描述方法,便于测试时观察。

/**
 * 遥控指令
 */
public interface ControlCommandV4 {
    void execute();
    String desc();
}

/**
 * 打开电视指令
 */
public class TVPowerOnCommandV4 implements ControlCommandV4 {

    private TelevisionV4 television;

    public TVPowerOnCommandV4(TelevisionV4 television) {
        this.television = television;
    }

    @Override
    public void execute() {
        System.out.println("执行【打开电视指令】...");
        television.on();
    }

    @Override
    public String desc() {
        return "打开电视指令";
    }
}

其他3个指令实现类似。
关键我们来看遥控板类,它是如何将变化的东西刨出去的

/**
 * V4版遥控板 - 4个按键的遥控板
 */
public class RemoteControlV4 {

    //4个按钮
    private ControlCommandV4[] commands = new ControlCommandV4[4];

    public void setCommand(int index, ControlCommandV4 command){
        System.out.println("设置遥控板的第"+index+"个按钮,功能是"+command.desc());
        commands[index] = command;
    }

    public void pressDownButton(int index){
        System.out.println("按下遥控板的第"+index+"个按钮....");
        System.out.println("执行的功能是"+commands[index].desc());
        commands[index].execute();
    }
}

这一版的遥控板,只和抽象指令耦合,所以厂商类API变化,以及扩展控制新的家电都不用在修改。
不过,V4版本的遥控板使用时需要注意,它要先配置相应的指令才能使用,比如

/**
 * 测试
 */
public class RemoteControlMainV4 {
    public static void main(String[] args) {
        TelevisionV4 television = new TelevisionV4();
        AirConditionerV4 airConditioner = new AirConditionerV4();

        RemoteControlV4 remoteControl = new RemoteControlV4();
        remoteControl.setCommand(0, new TVPowerOnCommandV4(television));
        remoteControl.setCommand(1, new TVPowerOffCommandV4(television));
        remoteControl.setCommand(2, new AirConditionerPowerOnCommandV4(airConditioner));
        remoteControl.setCommand(3, new AirConditionerPowerOffCommandV4(airConditioner));

        System.out.println();
        remoteControl.pressDownButton(0);
        System.out.println();
        remoteControl.pressDownButton(1);
        System.out.println();
        remoteControl.pressDownButton(2);
        System.out.println();
        remoteControl.pressDownButton(3);
    }
}

当然,客户不会直接使用裸遥控板的,出厂前会预植入每个按键相应的指令。不过,如果遥控板够高级的话,用户拿到遥控板后,可以自定义设置按键的指令。

扩展技巧,当我们希望按下按键时,什么都不做,我们可以使用空指令对象来初始化遥控板,从而避免空检查。

/**
 * 空指令-什么都不做
 */
public class NoneCommandV4 implements ControlCommandV4 {

    @Override
    public void execute() {
    }

    @Override
    public String desc() {
        return "空指令";
    }
}

很好,反过来看之前的需求,我们发现V4版的设计完美的满足了需求,大吉大利,今晚吃鸡。

感受下UML
在这里插入图片描述

好了,到这里,你可能也明白了啥是命令模式了,V4版就是利用命令模式将发送请求者和执行请求者解耦的。

定义

可以看出,命令模式中核心的就是有个叫命令的东西(我们的例子叫指令,都是一个意思),它们有统一的接口,
这样就可以统一方式调用命令;同时命令封装了具体的执行者,将请求转发到具体执行者上执行。

我们使用代码,来说明下命令模式
首先,来一个接受者,真正执行请求

/**
 * 接受者-也就是执行请求者
 */
public class Receiver {
    public void doSpecial(){
        System.out.println("接受者,实际执行请求....");
    }
}

接下来,定义命令接口

/**
 * 命令接口
 */
public interface Command {
    void execute();
}

实现具体的命令,也就是包装接受者

/**
 * 具体的命令
 */
public class ConcreteCommand implements Command {
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.doSpecial();
    }
}

有一个调用者,用它来调用命令

/**
 * 调用者-发起请求者
 */
public class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void doSomething(){
        command.execute();
    }
}

测试走一波

/**
 * 客户
 */
public class CommandMain {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        invoker.setCommand(new ConcreteCommand(new Receiver()));
        invoker.doSomething();

        invoker.setCommand(() -> {
            System.out.println("直接在命令里面就把事情做了...");
        });
        invoker.doSomething();
    }
}

把UML图拉出来看看
在这里插入图片描述

如果大家写过回调的话,可以感觉到invoker.setCommand()相当于注册回调,而调用invoker.doSomething()相当于触发回调。

撤销

正在吃鸡的过程中,客户告诉你说,他们对遥控板做了升级,加了一个撤销按钮,按下它可以撤销上一步的操作。
因为在V4版中使用的命名模式,那么实现撤销的功能就轻松了,来看看V5实现
厂商类一样,忽略。
指令接口中,加入撤销方法

/**
 * 遥控指令
 */
public interface ControlCommandV5 {
    void execute();//执行
    String desc();//描述
    void undo(); //撤销
}

接下来,每个指令都实现撤销方法。对于我们当前的指令来说,撤销的实现一点不复杂,做反操作即可

/**
 * 打开电视指令
 */
public class TVPowerOnCommandV5 implements ControlCommandV5 {

    private TelevisionV5 television;

    public TVPowerOnCommandV5(TelevisionV5 television) {
        this.television = television;
    }

    @Override
    public void execute() {
        System.out.println("执行【打开电视指令】...");
        television.on();
    }

    @Override
    public String desc() {
        return "打开电视指令";
    }

    @Override
    public void undo() {
        television.off();
    }
}

/**
 * 关闭电视指令
 */
public class TVPowerOffCommandV5 implements ControlCommandV5 {

    private TelevisionV5 television;

    public TVPowerOffCommandV5(TelevisionV5 television) {
        this.television = television;
    }

    @Override
    public void execute() {
        System.out.println("执行【关闭电视指令】...");
        television.off();
    }

    @Override
    public String desc() {
        return "关闭电视指令";
    }

    @Override
    public void undo() {
        television.on();
    }
}

/**
 * 打开空调指令
 */
public class AirConditionerPowerOnCommandV5 implements ControlCommandV5 {

    private AirConditionerV5 airConditionerV4;

    public AirConditionerPowerOnCommandV5(AirConditionerV5 airConditionerV4) {
        this.airConditionerV4 = airConditionerV4;
    }

    @Override
    public void execute() {
        System.out.println("执行【打开空调指令】...");
        airConditionerV4.turnOn();
    }

    @Override
    public String desc() {
        return "打开空调指令";
    }

    @Override
    public void undo() {
        airConditionerV4.turnOff();
    }
}

/**
 * 关闭空调指令
 */
public class AirConditionerPowerOffCommandV5 implements ControlCommandV5 {

    private AirConditionerV5 airConditionerV4;


    public AirConditionerPowerOffCommandV5(AirConditionerV5 airConditionerV4) {
        this.airConditionerV4 = airConditionerV4;
    }

    @Override
    public void execute() {
        System.out.println("执行【关闭空调指令】...");
        airConditionerV4.turnOff();
    }

    @Override
    public String desc() {
        return "关闭空调指令";
    }

    @Override
    public void undo() {
        airConditionerV4.turnOn();
    }
}

现在,我们来实现新版遥控版上的撤销按钮代码,仅仅需要加一个属性来记录上次操作的指令,当按下撤销按钮时,调用上次命令的撤销方法即可。

/**
 * V5版遥控板 - 4个功能按键和1个撤销按键遥控板
 */
public class RemoteControlV5 {

    //4个按钮
    private ControlCommandV5[] commands = new ControlCommandV5[4];
    //撤销按钮
    private ControlCommandV5 undoCommand;

    public void setCommand(int index, ControlCommandV5 command){
        System.out.println("设置遥控板的第"+index+"个按钮,功能是"+command.desc());
        commands[index] = command;
    }

    public void pressDownButton(int index){
        System.out.println("按下遥控板的第"+index+"个按钮....");
        System.out.println("执行的功能是"+commands[index].desc());
        commands[index].execute();
        undoCommand = commands[index];
    }

    public void pressDownUndoButton(){
        System.out.println("按下遥控板的撤销按钮....");
        undoCommand.undo();
    }

}

测试

/**
 * 测试
 */
public class RemoteControlMainV5 {
    public static void main(String[] args) {
        TelevisionV5 television = new TelevisionV5();
        AirConditionerV5 airConditioner = new AirConditionerV5();

        RemoteControlV5 remoteControl = new RemoteControlV5();
        remoteControl.setCommand(0, new TVPowerOnCommandV5(television));
        remoteControl.setCommand(1, new TVPowerOffCommandV5(television));
        remoteControl.setCommand(2, new AirConditionerPowerOnCommandV5(airConditioner));
        remoteControl.setCommand(3, new AirConditionerPowerOffCommandV5(airConditioner));

        System.out.println();
        remoteControl.pressDownButton(0);
        System.out.println();
        remoteControl.pressDownButton(1);
        System.out.println();
        remoteControl.pressDownUndoButton();
        System.out.println();
        remoteControl.pressDownButton(2);
        System.out.println();
        remoteControl.pressDownButton(3);
        System.out.println();
        remoteControl.pressDownUndoButton();

    }
}

不要太完美,你将代码修改完后,还能接着吃鸡,在老板眼中,这就是人才啊。

宏命令

过了不久,有来新的需求,最新的遥控版上要添加一种新的模式,即按某一个按钮,可以同时控制多个家电。
比如,你下班回到家,只需要按一次按钮,就能打开空调和电视,这又要如何实现?
其实这时候只需要创建一个新的命令实现,它来组合要控制的其他命令即可,命令模式中称这种命令为 宏命令。
这儿,我实现下宏命令,剩下的由你来试试。

/**
 * 宏命令,组合其他命令
 */
public class MacroCommandV6 implements ControlCommandV5{

    private List<ControlCommandV5> commandV5s;

    public MacroCommandV6(List<ControlCommandV5> commandV5s) {
        this.commandV5s = commandV5s;
    }

    @Override
    public void execute() {
        commandV5s.forEach(command -> command.execute());
    }

    @Override
    public String desc() {
        return commandV5s.stream().map(controlCommandV5 -> controlCommandV5.desc())
                .reduce((s1, s2) -> s1 + "," + s2).get();
    }

    @Override
    public void undo() {
        commandV5s.forEach(command -> command.undo());
    }
}

扩展示例

示例来自https://www.journaldev.com/1624/command-design-pattern.

代码是自解释的,就不用单独说明。直接上代码。
先定义Receiver和它的实现。

/**
 * 文件系统操作接口, 文件行为者
 */
public interface FileSystemReceiver {
    void openFile();
    void writeFile();
    void closeFile();
}

/**
 * Unix文件系统 具体行为实现
 */
public class UnixFileSystemReceiver implements FileSystemReceiver{
    @Override
    public void openFile() {
        System.out.println("Opening file in unix OS");
    }

    @Override
    public void writeFile() {
        System.out.println("Writing file in unix OS");
    }

    @Override
    public void closeFile() {
        System.out.println("Closing file in unix OS");
    }
}

/**
 * Windows文件系统 具体行为实现
 */
public class WindowsFileSystemReceiver implements FileSystemReceiver{
    @Override
    public void openFile() {
        System.out.println("Opening file in Windows OS");
    }

    @Override
    public void writeFile() {
        System.out.println("Writing file in Windows OS");
    }

    @Override
    public void closeFile() {
        System.out.println("Closing file in Windows OS");
    }
}

定义command和其实现

/**
 * 命令抽象
 */
public interface Command {
    void execute();
}

/**
 * 打开文件具体命令
 */
public class OpenFileCommand implements Command{
    private FileSystemReceiver fileSystemReceiver;

    public OpenFileCommand(FileSystemReceiver fileSystemReceiver) {
        this.fileSystemReceiver = fileSystemReceiver;
    }

    @Override
    public void execute() {
        fileSystemReceiver.openFile();
    }
}


/**
 * 打开文件具体命令
 */
public class CloseFileCommand implements Command{
    private FileSystemReceiver fileSystemReceiver;

    public CloseFileCommand(FileSystemReceiver fileSystemReceiver) {
        this.fileSystemReceiver = fileSystemReceiver;
    }

    @Override
    public void execute() {
        fileSystemReceiver.closeFile();
    }
}

/**
 * 打开文件具体命令
 */
public class WriteFileCommand implements Command{
    private FileSystemReceiver fileSystemReceiver;

    public WriteFileCommand(FileSystemReceiver fileSystemReceiver) {
        this.fileSystemReceiver = fileSystemReceiver;
    }

    @Override
    public void execute() {
        fileSystemReceiver.writeFile();
    }
}

然后是invoker

/**
 * 调用者
 */
public class FileInvoker {

    public Command command;

    public FileInvoker(Command command) {
        this.command = command;
    }

    public void execute(){
        command.execute();
    }
}

最后,客户端使用

/**
 * 客户端
 */
public class FileSystemClient {
    public static void main(String[] args) {
        //Creating the receiver object
        FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();

        //creating command and associating with receiver
        OpenFileCommand openFileCommand = new OpenFileCommand(fs);

        //Creating invoker and associating with Command
        FileInvoker file = new FileInvoker(openFileCommand);

        //perform action on invoker object
        file.execute();

        WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
        file = new FileInvoker(writeFileCommand);
        file.execute();

        CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
        file = new FileInvoker(closeFileCommand);
        file.execute();
    }
}

其中使用到一个工具类 FileSystemReceiverUtil, 它通过os.name属性获取底层操作系统的Receiver类型。

/**
 * 文件系统工具类
 */
public class FileSystemReceiverUtil {

    public static FileSystemReceiver getUnderlyingFileSystem(){
        String osName = System.getProperty("os.name");
        System.out.println("Underlying OS is:"+osName);
        if(osName.contains("Windows")){
            return new WindowsFileSystemReceiver();
        }else{
            return new UnixFileSystemReceiver();
        }
    }
}

输出结果

Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS

源码

https://gitee.com/cq-laozhou/design-pattern

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值