命令模式

定义

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

典型使用场景

  • 维护请求历史
  • 实现回调功能
  • 实现撤消功能

例子

借鉴一个游戏:哥布林小妖精是游戏的主人公之一,用户可以给他施展符咒,让它变大变小,也可让它隐身重现。
首先定义大小与隐身相关的常量:

// 可视的常量
public enum Visibility {
  VISIBLE("visible"), INVISIBLE("invisible"), UNDEFINED("");

  private String title;

  Visibility(String title) {
    this.title = title;
  }

  public String toString() {
    return title;
  }
}

// 大小的常量
public enum Size {
  SMALL("small"), NORMAL("normal"), LARGE("large"), UNDEFINED("");
  private String title;

  Size(String title) {
    this.title = title;
  }

  public String toString() {
    return title;
  }
}

目标可以是哥布林,也可以是其他,因此提供一个基类,哥布林是其一个子类:

// 目标的抽象基类
public abstract class Target {
  private Size size;
  private Visibility visibility;

  public Size getSize() {
    return size;
  }
  public void setSize(Size size) {
    this.size = size;
  }

  public Visibility getVisibility() {
    return visibility;
  }

  public void setVisibility(Visibility visibility) {
    this.visibility = visibility;
  }

  public abstract String toString();

  public void printStatus() {
    System.out.println(String.format("%s, [size=%s] [visibility=%s]", this, getSize(),
        getVisibility()));
    System.out.println();
  }
}

// 哥布林小妖精是目标实例
public class Goblin extends Target {
  public Goblin() {
    setSize(Size.NORMAL);
    setVisibility(Visibility.VISIBLE);
  }

  public String toString() {
    return "Goblin";
  }
}

定义命令的抽象对象,隐藏和缩小符咒都是继承自这个对象,每个对象都有执行、撤销和重做三个方法:

public abstract class Command {
  public abstract void execute(Target target); // 执行
  public abstract void undo();  // 撤销
  public abstract void redo();  // 重做
  public abstract String toString();
}

// 具体命令:隐藏符咒
public class InvisibilitySpell extends Command {
  private Target target;

  public void execute(Target target) {
    target.setVisibility(Visibility.INVISIBLE);
    this.target = target;
  }

  public void undo() {
    if (target != null) {
      target.setVisibility(Visibility.VISIBLE);
    }
  }

  public void redo() {
    if (target != null) {
      target.setVisibility(Visibility.INVISIBLE);
    }
  }

  public String toString() {
    return "Invisibility spell";
  }
}

// 具体命令:缩小符咒
public class ShrinkSpell extends Command {

  private Size oldSize;
  private Target target;

  public void execute(Target target) {
    oldSize = target.getSize();
    target.setSize(Size.SMALL);
    this.target = target;
  }

  public void undo() {
    if (oldSize != null && target != null) {
      Size temp = target.getSize();
      target.setSize(oldSize);
      oldSize = temp;
    }
  }

  public void redo() {
    undo();
  }

  public String toString() {
    return "Shrink spell";
  }
}

有了目标以及命令对象,创建一个类,维护命令列表:

// 命令的调用者
public class Wizard {

    // 双向队列
  private Deque undoStack = new LinkedList<>();
  private Deque redoStack = new LinkedList<>();

  public Wizard() {}

  // 执行符咒
  public void castSpell(Command command, Target target) {
    System.out.println(this + " casts " + command + " at " + target);
    command.execute(target);
    undoStack.offerLast(command);
  }

  // 撤销上次执行的符咒
  public void undoLastSpell() {
    if (!undoStack.isEmpty()) {
      Command previousSpell = undoStack.pollLast();
      redoStack.offerLast(previousSpell);
      System.out.println(this + " undoes " + previousSpell);
      previousSpell.undo();
    }
  }

  // 重新执行上个符咒
  public void redoLastSpell() {
    if (!redoStack.isEmpty()) {
      Command previousSpell = redoStack.pollLast();
      undoStack.offerLast(previousSpell);
      System.out.println(this + " redoes " + previousSpell);
      previousSpell.redo();
    }
  }

  public String toString() {
    return "Wizard";
  }
}

进行测试:

public class App {
  public static void main(String[] args) {
    Wizard wizard = new Wizard();
    Goblin goblin = new Goblin();

    goblin.printStatus();

    wizard.castSpell(new ShrinkSpell(), goblin);
    goblin.printStatus();

    wizard.castSpell(new InvisibilitySpell(), goblin);
    goblin.printStatus();

    wizard.undoLastSpell();
    goblin.printStatus();

    wizard.undoLastSpell();
    goblin.printStatus();

    wizard.redoLastSpell();
    goblin.printStatus();

    wizard.redoLastSpell();
    goblin.printStatus();
  }
}

分析

命令模式可以维护操作历史列表,便于进行撤销、重做等操作。

参考

iluwatar/java-design-patterns

你可能感兴趣的:(命令模式)