设计模式十八-- 命令模式

438 阅读4分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

设计模式

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

命令模式

什么是命令模式?

命令模式将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。它就像遥控器一样,我们只需要按遥控器的按键发出命令来实现我们的目标,而我们无需知道命令的执行细节。

我们都玩过收音机(00后可能没见过),假设收音机有2个按键:播放键、倒带键。而二次按播放键就是停止键,二次按倒带键就是停止倒带。

接下来就以收音机为例,讲解我们的命令模式。

首先创建命令的接收者Radio.java,实现命令的四个功能:播放、停止、倒带、停止倒带

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 17:45
 */public class Radio {
    public void play() {
        System.out.println("开始播放");
    }
​
    public void stop() {
        System.out.println("停止播放");
    }
​
    public void rewind() {
        System.out.println("开始倒带");
    }
​
    public void stopRewind() {
        System.out.println("停止倒带");
    }
}

定义一个抽象命令接口Command.java,这个接口一个是execute执行(第一次按键),一个是undo取消(第二次按键)。

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 17:17
 */public interface Command {
    public void execute();
​
    public void undo();
}

定义具体命令PlayCommand,通过调用接收者Radio,来完成命令的执行。

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 17:36
 */public class PlayCommand implements Command {
    private Radio redio;
​
    public void execute() {
        redio.play();
    }
​
    public void undo() {
        redio.stop();
    }
​
}

RewindCommand.java,通过调用接收者Radio,来完成命令的执行

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 17:38
 */public class RewindCommand implements Command {
    private Radio redio;
​
    public void execute() {
        redio.rewind();
    }
​
    public void undo() {
        redio.stopRewind();
    }
}

请求者角色,按键Keypad,它拥有很多命令对象,来实现命令。

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 18:14
 */public class Keypad {
    private Command playCommand;
    private Command rewindCommand;
​
    public void setPlayCommand(Command playCommand) {
        this.playCommand = playCommand;
    }
​
    public void setRewindCommand(Command rewindCommand) {
        this.rewindCommand = rewindCommand;
    }
​
    public void play() {
        playCommand.execute();
    }
​
    public void rewind() {
        rewindCommand.execute();
    }
​
    public void stop() {
        playCommand.undo();
    }
​
    public void stopRewind() {
        rewindCommand.undo();
    }
}

main

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/27 18:16
 */public class Main {
    public static void main(String[] args) {
        Radio redio = new Radio();
        Command playCommand = new PlayCommand(redio);
        Command rewindCommand = new RewindCommand(redio);
        Keypad keypad = new Keypad();
        keypad.setPlayCommand(playCommand);
        keypad.setRewindCommand(rewindCommand);
        keypad.rewind();
        keypad.stopRewind();
        keypad.play();
        keypad.stop();
​
    }
}

最终我们通过按按键实现了四个功能。这就是命令模式。

在这里我们按键Keypad拥有一系列的命令对象playCommand和rewindCommand,而我们的接收者Radio具体实现了play、stop、rewind、stopRewind四个功能,我们的命令对象通过组合Radio来调用他的功能,对我们请求者来说,只需要按按键来实现我们想要的功能,而无需关心Radio具体怎么实现的这些功能。

这里我们将按键与收音机内核解耦了,如果想扩展新命令,只需在此基础上增加命令即可。

源码中的命令模式

JDK中的Runnable

package java.lang;
​
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable就是相当于我们命令模式中的抽象命令类角色,里面定义了一个执行命令的方法run,相当于我们例子中的execute方法。

我们可以自定义具体命令角色,还是基于上述的例子上写

package com.wangscaler.command;
​
/**
 * @author WangScaler
 * @date 2021/7/28 15:17
 */public class TurnOnThread implements Runnable {
    private Radio radio;
​
    public TurnOnThread(Radio radio) {
        this.radio = radio;
    }
​
    public void run() {
        radio.play();
    }
}

我们的接收者角色还是上述例子中的Radio。这次Thread可以充当我们的请求者角色,通过start()来实现命令的下发,Thread的源码如下

package java.lang;
​
public class Thread implements Runnable {
    
  public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
​
    private native void start0();
}

总结

命令模式的角色

    1. 抽象命令类角色:定义抽象的执行命令的方法execute()。
    1. 具体命令类角色:组合接收者,通过调用接受者的功能来实现命令的执行。
    1. 接收者角色:命令的真正实现者,功能的具体实现。
    1. 请求者角色:具有很多命令对象,通过命令对象完成命令的下发。

缺点:

需要大量的具体命令类。

何时使用:

比如我们的万能遥控器,我们怎么控制空调、电视、电扇呢?我们的空调、电视、电扇都可以提供命令对象API,我们的万能遥控器可以通过这些命令对象,实现对空调、电视、电扇的控制。

参考资料