设计模式7:命令模式

之前学习过的设计模式,有对行为的封装(策略模式),有对创建的封装(工厂方法和抽象工厂),今天要学的命令模式(Command DP)则是对请求(request)的封装。
或许有人会觉得请求也是一种行为,但两个概念还是大不相同。行为只是一个宽泛的概念,而请求就具体得多。
把请求抽象成命令类,是命令模式的灵魂。请求本身就可以看做一个命令(Command),由调用者(Invoker)发出,然后还得有目标(Target)来接受这个命令。
此外,因为有了具体的命令,所以可以和队列配合使用,还可以撤销(Undo)和记录(Log)。

代码:
抽象类Command,在执行以外,还能撤销和恢复:

/**
 * 
 * Interface for Commands.
 * 
 */
public abstract class Command {

  public abstract void execute(Target target);

  public abstract void undo();

  public abstract void redo();

  @Override
  public abstract String toString();

}

有一个巫师(Wizard)作为调用者,会施法:

/**
 * 
 * Wizard is the invoker of the commands
 *
 */
public class Wizard {

  private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class);

  private Deque undoStack = new LinkedList<>();
  private Deque redoStack = new LinkedList<>();

  public Wizard() {}

  /**
   * Cast spell
   */
  public void castSpell(Command command, Target target) {
    LOGGER.info("{} casts {} at {}", this, command, target);
    command.execute(target);
    undoStack.offerLast(command);
  }

  /**
   * Undo last spell
   */
  public void undoLastSpell() {
    if (!undoStack.isEmpty()) {
      Command previousSpell = undoStack.pollLast();
      redoStack.offerLast(previousSpell);
      LOGGER.info("{} undoes {}", this, previousSpell);
      previousSpell.undo();
    }
  }

  /**
   * Redo last spell
   */
  public void redoLastSpell() {
    if (!redoStack.isEmpty()) {
      Command previousSpell = redoStack.pollLast();
      undoStack.offerLast(previousSpell);
      LOGGER.info("{} redoes {}", this, previousSpell);
      previousSpell.redo();
    }
  }

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

注意这里面undo和redo的实现。
首先是有两个Deque,undoStack是用来保存命令的历史,当要求撤销的时候,弹出上一个命令,然后做撤销。只是光这样还不够,因为还得支持redo。
redo就相当于对undo的undo(当然具体看实现,也不一定就是取消undo,不过从概念上来讲是这样,就好比这里的undo也不一定就是撤销,因为由具体的Command来决定),也就是说redoStack保存的是执行undo的命令的历史,当要redo上一个undo的时候,弹出该命令然后执行redo操作。然而还没完,这个操作应该也要能被undo,因此又把该命令加入undoStack。
具体的命令类:

/**
 * 
 * Enumeration for target visibility.
 *
 */
public enum Visibility {

  VISIBLE("visible"), INVISIBLE("invisible"), UNDEFINED("");

  private String title;

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

  @Override
  public String toString() {
    return title;
  }
}
/**
 * 
 * InvisibilitySpell is a concrete command
 *
 */
public class InvisibilitySpell extends Command {

  private Target target;

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

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

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

  @Override
  public String toString() {
    return "Invisibility spell";
  }
}
/**
 *
 * Enumeration for target size.
 *
 */
public enum Size {

  SMALL("small"), NORMAL("normal"), LARGE("large"), UNDEFINED("");

  private String title;

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

  @Override
  public String toString() {
    return title;
  }
}
/**
 * 
 * ShrinkSpell is a concrete command
 *
 */
public class ShrinkSpell extends Command {

  private Size oldSize;
  private Target target;

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

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

  @Override
  public void redo() {
    undo();
  }

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

抽象target类:

/**
 * 
 * Base class for spell targets.
 *
 */
public abstract class Target {

  private static final Logger LOGGER = LoggerFactory.getLogger(Target.class);

  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;
  }

  @Override
  public abstract String toString();

  /**
   * Print status
   */
  public void printStatus() {
    LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
  }
}

具体的target类:

/**
 * 
 * Goblin is the target of the spells
 *
 */
public class Goblin extends Target {

  public Goblin() {
    setSize(Size.NORMAL);
    setVisibility(Visibility.VISIBLE);
  }

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

}

测试:

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();
  }
}

可以看到,巫师可以使用两个不同的法术,而且还可以undo和redo,并且不用知道之前用的是什么法术。
个人觉得很多IDE可能会采用这个模式。

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