设计模式学习笔记(14)备忘录

本文实例代码:https://github.com/JamesZBL/java_design_patterns

备忘录模式(Memento),别名为快照模式(Snapshot),是的行为型模式的一种。它的主要特点是创建一个特殊对象用于保存其他若干个对象在某一刻的状态,以便在需要获得该状态的时候能够及时恢复。

实例

一粒种子从被播种到发芽再到开花会经历很长的时间,虽然我们用肉眼看不出来它在长大,但它的确每时每刻都在生长。植物生长过程中的每一刻的状态都是不完全相同的,所以我们可以用一个“快照”来保存植物的若干个状态。

我们暂时不考虑植物学上各种微观的状态,所以简单定义一下植物,它的状态由高度和重量组成,同时,植物区分不同的生长阶段:

Plant.java

public interface Plant {

  int getWeight();

  int getHeight();

  FlowerType getType();
}

定义一个花朵类 Flower,它实现植物 Plant 接口,以获得其生长过程中某一时刻的状态,同时花朵可以生长,为了模拟生长过程,我们自定义一个生长速度计算公式,并且每次调用 growing 方法都会使花朵的阶段向下移阶段跳转,比如花朵处于种子阶段的时候,生长一次就处于了 “花苞” 的阶段。

模拟花朵生长的方法:

public void growing() {
  setWeight(getWeight() * 2);
  setHeight(getHeight() * 3);
  switch (type) {
    case SEED: {
      setType(FlowerType.BURGEON);
      break;
    }
    case BURGEON: {
      setType(FlowerType.BUD);
      break;
    }
    case BUD: {
      setType(FlowerType.BLOOM);
      break;
    }
    case BLOOM: {
      setType(FlowerType.DEAD);
      setHeight(0);
      setWeight(0);
      break;
    }
    default:
      break;
  }
}

在花朵类中定义一个私有的静态内部类 FlowerMemento,这个类负责记录花朵的生长状态:

private static class FlowerMemento implements Plant {

  private FlowerType type;
  private int height;
  private int weight;

  private FlowerMemento(FlowerType type, int height, int weight) {
    this.type = type;
    this.height = height;
    this.weight = weight;
  }

  @Override
  public int getWeight() {
    return weight;
  }

  @Override
  public int getHeight() {
    return height;
  }

  @Override
  public FlowerType getType() {
    return type;
  }
}

用一个枚举类来定义花朵的生长阶段:

FlowerType.java

public enum FlowerType {

  SEED("种子"), BURGEON("发芽"), BUD("花苞"), BLOOM("开放"), DEAD("凋零");

  private String name;

  FlowerType(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return name;
  }
}

为了便于理清这几个类之间的关系,现在给出完整的 Flower 类:

Flower.java

public class Flower implements Plant {

  private FlowerType type;
  private final String name;
  private int height;
  private int weight;

  public void growing() {
    // 上文给出了完整的此方法
  }

  FlowerMemento getMemento() {
    return new FlowerMemento(getType(), getHeight(), getWeight());
  }

  void setMemento(Plant plant) {
    FlowerMemento flowerMemento = (FlowerMemento) plant;
    setType(flowerMemento.getType());
    setHeight(flowerMemento.getHeight());
    setWeight(flowerMemento.getWeight());
  }

  @Override
  public String toString() {
    return String.format("名称:%s\t状态:%s\t质量:%d克\t高度:%d厘米", getName(), getType(), getWeight(), getHeight());
  }

  public Flower(FlowerType type, String name, int height, int weight) {
    this.type = type;
    this.name = name;
    this.height = height;
    this.weight = weight;
  }

  // getter & setter ...

  private static class FlowerMemento implements Plant {

    private FlowerType type;
    private int height;
    private int weight;

    private FlowerMemento(FlowerType type, int height, int weight) {
      this.type = type;
      this.height = height;
      this.weight = weight;
    }

    // getter & setter ...
  }
}

最后模拟一下花朵的生长过程:

App.java

public class Application {

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

  public static void main(String[] args) {

    Flower flower = new Flower(FlowerType.SEED, "水仙花", 1, 2);
    LOGGER.info(flower.toString());
    flower.growing();
    LOGGER.info(flower.toString());
    flower.growing();
    LOGGER.info(flower.toString());
    flower.growing();
    LOGGER.info(flower.toString());
    flower.growing();
    LOGGER.info(flower.toString());
    flower.growing();
    LOGGER.info(flower.toString());
  }
}

总结

通过这个例子可以总结出,备忘录模式的主要特点有:

在不修改被捕获对象的原有状态前提下,抓取一个对象的内部状态,并在独立于该对象的对象中保存被捕获对象的状态。所以应该使用静态内部类作为快照保存的实现,因为对象的状态和对象没有直接紧密的联系,而是相对的独立联系。

为了保证状态持有者的数据不允许被除了 “被捕获对象” 之外的对象访问到,应当将状态持有者的类定义为 “被捕获对象类” 的私有类。

在许多的软件中,都需要保存当前工作进度的功能,所以这些正是备忘录模式的使用场景:
– 游戏软件中的存档
– 字处理软件(比如 MS Office)中 “撤销上一步” 的操作
– 浏览器中的返回上一页
– 数据库中的事务回滚

为了避免每次保存状态和恢复状态耗费较多内存资源,可以将备忘录模式和之前的文章中提到的原型模式结合使用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.