设计模式-命令模式

“小度,小度,热死了请打开空调~”,“小度,小度,主人回家了~”,“小度,小度,播放一首炸雷~”,像小度类似的智能产品正在逐步改善我们的生活,只要我们一声令下,产品立马执行,毫不拖延。就如同老板的命令一样,系统需接收到命令后经过一系列分析后,会自动选择执行哪些命令,而发令者完全不关心内部是执行了哪些动作以及哪个具体方法。比如“小度的主人回家了”,小度内部接受到指令后会自动分析出需要打开灯光,打开窗帘,打开中央空调,播放一首著名的萨克斯独奏-回家以及检查房门是否已关闭等内容。这一切都是不需要发令者感知的 ,发令者只需要发出指令,具体该执行哪些功能由指令控制器来分析完成。
对于这样的场景下,如何从代码层面上很好的解决呢?我的第一个想法那就是封装,面向对象那些事情都离不开封装。封装系统向外透出的功能,我们很自然能够想到“接口”、“API”这些概念。发送者发送的指令可以是由系统提供一系列API,比如电商系统的提单就调用类似/order/submit的API,加入购物车就调用/cart/add/product/的API。这些API的具体执行逻辑在调用方也是解耦(不感知)的,其内部流程化逻辑也是多个系统功能之间的紧密配合。但尽管如此,API或接口的这种方式依然不能够解决“小度”类似场景的问题。为何呢?这是因为小度的指令执行不是流程化的东西,具体的说就是一条指令可能是“打开空调”,也可能是“打开空调,打开灯光”,也可能是“打开灯光,关闭窗帘”,这种功能之间的组合是非流程化的东西,无法穷尽就不适合通过API、接口进行封装。更甚者我如果说“小度,小度,撤销上一次的操作”,我想问问你使用API、接口能完成这项工作吗?明显不能。但是我们今天要讲述的**命令模式(Command Pattern)**就可以。

一、命令模式的概念

命令模式,又称作动作模式或事务模式。其一般定义(稍有改动)如下:

将请求封装为命令对象,使得客户端对系统的请求参数化、具体请求可以在运行时更改、排队、记录,并且能够提供命令的撤销和恢复功能。

命令模式定义中,关于第一句我个人认为有思考的空间。我认为封装的不应该是客户端请求,而是系统所提供的一系列可执行业务逻辑,比如上文中“灯光控制逻辑”、“窗帘控制逻辑”等。封装客户端请求注定会带来很多扩展性问题,并且最关键的是你根本无法预知客户端会如何请求,以及请求哪些功能。最后带来的问题必然是无法支持客户端变化无端的需要,需要不端增加对应的组合命令对象,引起类的剧增。所以,我认为应该将可执行单元封装为命令对象。
系统所提供的的功能向外透出为一系列的可执行的命令对象,而客户端只需要通过参数告知系统应该执行哪些命令,或通过其他可解析参数的方式(由系统解析为指令)来完成请求即可。
既然系统功能已经封装为一个个命令(Command )组成,那么这些命令自然就涉及到运行时更改(全部取消或暂停执行)、排队(多个未执行命令排队顺序执行)、记录(记录下历史已执行的命令)、撤销(取消上一次执行的命令)、恢复(恢复上一次执行的命令)等操作。那这部分职责就应该由命令执行器(Command Executor)来负责。
概念总结:

  • 命令模式能够解决客户端多样化功能组合请求,且需具备事务回滚处理能力的场景。
  • 命令模式将系统功能拆分独立命令,客户端发出命令来执行其所需要的功能
  • 命令模式将系统功能逻辑及客户端请求之间进行了解耦。Command仅需关注本身业务逻辑的实现,而客户端解析命令以及命令之间的相关调配由CommandExecutor负责。

二、应用实践

为了更好的理解、应用命令模式,本章将给出一个能够使用命令模式解决相关问题的案例。
案例背景就是关于上文中提到的小度智能家居的例子。小度系统涉及的功能包括开关机控制模块、灯光控制模块,窗帘控制模块,以及门锁检查模块(为简化代码,其他功能暂不考虑)。为了更好处理主人的命令,小度系统还需要提供一个智能控制器来负责命令之间的排队、撤销、恢复、解析等功能。
命令接口:

public interface Command {
    // 执行当前命令
    void execute();
    // 恢复当前命令--命令需具备回滚能力
    void undo();
}

打开灯光命令:

public class TurnOnLightCommand implements Command {

    public static final TurnOnLightCommand turnOnLightCommandIns = new TurnOnLightCommand();

    private final LightControl lightControl;

    public TurnOnLightCommand() {
        this.lightControl = new LightControl();
    }

    @Override
    public void execute() {
        lightControl.turnOn();
    }

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

关闭灯光命令:

public class TurnOffLightCommand implements Command {

    public static final TurnOffLightCommand turnOffLightCommandIns = new TurnOffLightCommand();

    private final LightControl lightControl;

    public TurnOffLightCommand() {
        this.lightControl = new LightControl();
    }

    @Override
    public void execute() {
        lightControl.turnOff();
    }

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

打开窗帘命令:

public class OpenCurtainCommand implements Command {

    public static final OpenCurtainCommand openCurtainCommandIns = new OpenCurtainCommand();

    private final CurtainControl curtainControl;

    public OpenCurtainCommand() {
        this.curtainControl = new CurtainControl();
    }

    @Override
    public void execute() {
        curtainControl.open();
    }

    @Override
    public void undo() {
        curtainControl.close();
    }
}

关闭窗帘命令:

public class CloseCurtainCommand implements Command {

    public static final CloseCurtainCommand closeCurtainCommandIns = new CloseCurtainCommand();

    private final CurtainControl curtainControl;

    public CloseCurtainCommand() {
        this.curtainControl = new CurtainControl();
    }

    @Override
    public void execute() {
        curtainControl.close();
    }

    @Override
    public void undo() {
        curtainControl.open();
    }
}

智能家居控制器代码实现:

/**
 * 智能家居控制系统
 */
public class SmartHomeControl {

    private final Queue<Command> commandQueue;      // 命令队列

    public SmartHomeControl() {
        commandQueue = new LinkedList<>();
    }

    public void request(String strCommand) {
        // 先解析出命令列表
        List<Command> commands = parseCommand(strCommand);
        this.request(commands);
    }

    public void request(List<Command> commands) {
        if(commands == null || commands.isEmpty()) {
            return;
        }
        commandQueue.addAll(commands);      // 添加到队列中等待执行
    }

    public void executorCommands() {        // 逐个执行队列中的命令
        while (!commandQueue.isEmpty()) {
            Command command = commandQueue.poll();
            command.execute();
        }
    }

    public void undorCommands() {           // 逐个取消队列中的命令
        while (!commandQueue.isEmpty()) {
            Command command = commandQueue.poll();
            command.undo();
        }
    }

    private List<Command> parseCommand(String strCommand) {
        // 根据strCommand解析出对应的指令列表
        return new ArrayList<>();
    }
}

客户端执行打开灯光,和恢复指令操作:

public class Client {
    public static void main(String[] args) {
        // 创建智能家居模块
        SmartHomeControl smartHomeControl = new SmartHomeControl();

        // 发出打开窗帘命令
        Command turnOnLightCommand = TurnOnLightCommand.turnOnLightCommandIns;

        smartHomeControl.request(Collections.singletonList(turnOnLightCommand));
        smartHomeControl.executorCommands();        // 执行命令

        smartHomeControl.request(Collections.singletonList(turnOnLightCommand));
        smartHomeControl.undorCommands();           // 执行取消命令
    }
}

设计模式-命令模式_第1张图片
从代码及类图上可以看出,通过将各个业务逻辑单元封装为一个个Command类,由SmartHomeControl类来负责命令之间的入队、执行等操作,实现了命令调用者与实际业务逻辑之间的解耦合,提高了重用的方法。客户端可以任意选择执行不同命令的组合。
命令模式的优点:

  • 将调用者与具体执行者解耦
  • 支持将多个命令装配为一个复合命令
  • 支持操作的事务能力
  • 通过命令控制器可以做很多东西,比如限流、拦截器、校验以及日志记录等等
  • 符合开闭原则,新增命令很容易,不会影响原有功能

命令模式的缺点:

  • 系统功能逐步复杂时,命令类可能会很多。

你可能感兴趣的:(设计模式(JAVA),设计模式,命令模式)