今天要跟大家聊的是命令模式(Order pattern),这种模式其实很好理解:每一个指令所完成的行为都是不一样的,为了减少 if-else 判断,同时也是为了将命令请求者与执行者解耦,所以才引入命令模式的概念。

今天就将以上一篇我所理解的设计模式系列·第19篇·基于备忘录模式实现游戏存档与读取中口袋妖怪游戏的例子来为大家介绍命令模式。

第20篇·基于命令模式实现RPG游戏移动指令 - 图1

1. 定义

命令模式是一种行为型设计模式,它的定义是:“将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

封装请求有一个目的,就是将命令请求者与执行者解耦,这对项目的拓展有极大好处,否则就将是一大堆 if-else 以及超长超级冗杂的处理请求代码块。

2. 类图

在命令模式中,有这样几种角色:

  • 抽象命令者(Command):定义了执行命令的方法,同时会组合一个命令接收者,用于将请求命令转发给接收者去执行
  • 具体命令者(Concrete Command):抽象命令者的具体实现类,它将调用命令接收者的方法来完成命令要执行的命令
  • 命令接收者(Receiver):真正执行具体命令的角色
  • 请求者(Invoker):命令的发送者,它通常拥有很多的命令对象,同时它不直接访问命令接受者

命令模式的类图如下:

第20篇·基于命令模式实现RPG游戏移动指令 - 图2

3. 示例

上文说到玩家游玩经典 RPG 游戏口袋妖怪,其实在口袋妖怪的操作界面上都是命令模式的最佳实践,如下图:

第20篇·基于命令模式实现RPG游戏移动指令 - 图3

口袋妖怪操作界面中有6个比较常用的按钮,分别是四个方向键以及 A 确认键和 B 返回键,每个按键的功能都是不一样的,我们可以通过封装这些按键来实现一个命令模式。

首先我们定义命令接受者,也就是这些按钮真正执行的逻辑,代码如下:

  1. public interface Receiver {
  2. void action();
  3. }
  4. public class ReceiverUp implements Receiver{
  5. @Override
  6. public void action() {
  7. System.out.println("方向键UP:角色向上移动一步");
  8. }
  9. }
  10. public class ReceiverDown implements Receiver{
  11. @Override
  12. public void action() {
  13. System.out.println("方向键Down:角色向下移动一步");
  14. }
  15. }
  16. public class ReceiverLeft implements Receiver{
  17. @Override
  18. public void action() {
  19. System.out.println("方向键Left:角色向左移动一步");
  20. }
  21. }
  22. public class ReceiverRight implements Receiver{
  23. @Override
  24. public void action() {
  25. System.out.println("方向键Right:角色向右移动一步");
  26. }
  27. }

然后定义命令类,也就是在游戏中展示给玩家的那几个按钮,按钮执行的命令通过持有 Receiver 对象来做命令的转发,可以起到解耦的作用,其代码如下:

  1. public abstract class Command {
  2. private Receiver receiver;
  3. private String name;
  4. public Command(Receiver receiver, String name) {
  5. this.receiver = receiver;
  6. this.name = name;
  7. }
  8. public void execute() {
  9. receiver.action();
  10. }
  11. }
  12. public class CommandUp extends Command {
  13. public CommandUp() {
  14. super(new ReceiverUp(), "方向键UP");
  15. }
  16. }
  17. public class CommandDown extends Command {
  18. public CommandDown() {
  19. super(new ReceiverDown(), "方向键DOWN");
  20. }
  21. }
  22. public class CommandLeft extends Command {
  23. public CommandLeft() {
  24. super(new ReceiverLeft(), "方向键LEFT");
  25. }
  26. }
  27. public class CommandRight extends Command {
  28. public CommandRight() {
  29. super(new ReceiverRight(), "方向键RIGHT");
  30. }
  31. }

然后编写请求者角色:

public class Invoker {
    private Command command;

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

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

最后编写测试代码:

public class Test {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        System.out.println("玩家按下方向键UP");
        invoker.setCommand(new CommandUp());
        invoker.executeCommand();

        System.out.println("玩家按下方向键DOWN");
        invoker.setCommand(new CommandDown());
        invoker.executeCommand();

        System.out.println("玩家按下方向键LEFT");
        invoker.setCommand(new CommandLeft());
        invoker.executeCommand();

        System.out.println("玩家按下方向键RIGHT");
        invoker.setCommand(new CommandRight());
        invoker.executeCommand();
    }
}

输出结果为:

玩家按下方向键UP
方向键UP:角色向上移动一步
玩家按下方向键DOWN
方向键DOWN:角色向下移动一步
玩家按下方向键LEFT
方向键LEFT:角色向左移动一步
玩家按下方向键RIGHT
方向键RIGHT:角色向右移动一步

4. 使用场景

命令模式的使用场景是:

  • 命令模式的应用场景其实十分广泛,比如上面案例中提到的游戏指令实现、比如需要通过统一入口做命令转发的场景等等

5. 小结

本文讲述了命令模式,它的定义是——将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

命令模式的优点是:

  • 将命令请求者与执行者解耦
  • 命令者可以很方便地进行扩展

命令模式的缺点是:

  • 每一个具体命令都需要拓展一个实现类,可能导致 Command 类膨胀

6. 参考资料

最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。