【设计模式】行为型模式其六: 备忘录模式

备忘录模式

备忘录模式概述

备忘录模式——软件中的“后悔药”——撤销(Undo)

image.png

  • 通过使用备忘录模式可以让系统恢复到某一特定的历史状态
  • 首先保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态

模式定义

备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。

  • 别名为标记(Token)模式
  • 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
  • 在很多软件所提供的撤销(Undo)操作中就使用了备忘录模式

备忘录模式结构

image.png

备忘录模式包含以下3个角色:

  • Originator(原发器) 需要保存状态的对象
  • Memento(备忘录) 由原发器创建,保存原发器的状态
  • Caretaker(负责人) 知道并负责存储备忘录

备忘录模式实现

  • 除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法
  • 如果允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义
  • 理想的情况是只允许生成该备忘录的原发器访问备忘录的内部状态

Java语言实现

(1) 将Memento类与Originator类定义在同一个包(package)中来实现封装,使用默认可见性定义Memento类,即保证其在包内可见
(2) 将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据

实例学习

某软件公司要使用Java语言开发一款可以运行在Android平台的触摸式中国象棋软件。

由于考虑到有些用户是“菜鸟”,经常不小心走错棋;还有些用户因为不习惯使用手指在手机屏幕上拖动棋子,常常出现操作失误,因此该中国象棋软件要提供“悔棋”功能,在用户走错棋或操作失误后可恢复到前一个步骤。

为了实现“悔棋”功能,现使用备忘录模式来设计该中国象棋软件

备忘录类

该类用于存储棋子的当前状态,作为备忘录

//象棋棋子备忘录类:备忘录
class ChessmanMemento {
   private String label;
   private int x;
   private int y;


   public ChessmanMemento(String label,int x,int y) {
      this.label = label;

      this.x = x;

      this.y = y;

   }



   public void setLabel(String label) {
      this.label = label; 
   }

   public void setX(int x) {
      this.x = x; 
   }

   public void setY(int y) {
      this.y = y; 
   }




   public String getLabel() {
      return (this.label); 
   }

   public int getX() {
      return (this.x); 
   }



   public int getY() {
      return (this.y); 
   }  
}

原发器类

// 该类是原发器类,定义需要被保存的状态
采用保存状态的方式实现备忘录模式,还有一种方式,在下面介绍

//象棋棋子类:原发器
public class Chessman {
   private String label;
   private int x;
   private int y;


   public Chessman(String label,int x,int y) {
      this.label = label;

      this.x = x;

      this.y = y;

   }


   public void setLabel(String label) {
      this.label = label; 
   }
   public void setX(int x) {
      this.x = x; 
   }
   public void setY(int y) {
      this.y = y; 
   }
   public String getLabel() {
      return (this.label); 
   }


   public int getX() {
      return (this.x); 
   }
   public int getY() {
      return (this.y); 
   }
  
    //保存状态
   public ChessmanMemento save() {
      return new ChessmanMemento(this.label,this.x,this.y);
   }
   
   //恢复状态
   public void restore(ChessmanMemento memento) {
      this.label = memento.getLabel();
      this.x = memento.getX();
      this.y = memento.getY();
   }
}

负责人

// 负责人类需要知道维护的备忘录列表,便于获取原发器的不同时刻的状态
// 它的职责只是保存状态

public class MementoCaretaker {
    //定义一个集合来存储多个备忘录
   private ArrayList<ChessmanMemento> mementolist = new ArrayList<ChessmanMemento>();


   public ChessmanMemento getMemento(int i) {
      return (ChessmanMemento)mementolist.get(i);
   }

   public void setMemento(ChessmanMemento memento) {
      mementolist.add(memento);
   }


}

调用类

public class Client {
private static int index = -1; //定义一个索引来记录当前状态所在位置
   private static MementoCaretaker mc = new MementoCaretaker();


   public static void main(String args[]) {
      Chessman chess = new Chessman("车",1,1);
      play(chess);      
      chess.setY(4);
      play(chess);
      chess.setX(5);
      play(chess);   
      undo(chess,index);
      undo(chess,index); 
      redo(chess,index);
      redo(chess,index);
   }
   
    //下棋
   public static void play(Chessman chess) {
      mc.setMemento(chess.save()); //保存备忘录
      index ++; 
      System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行" + "第" + chess.getY() + "列。");
   }




   //悔棋
   public static void undo(Chessman chess,int i) {
      System.out.println("******悔棋******");
      index --; 
      chess.restore(mc.getMemento(i-1)); //撤销到上一个备忘录
      System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行" + "第" + chess.getY() + "列。");
   }



   //撤销悔棋
   public static void redo(Chessman chess,int i) {
      System.out.println("******撤销悔棋******");    
      index ++; 
      chess.restore(mc.getMemento(i+1)); //恢复到下一个备忘录
      System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行" + "第" + chess.getY() + "列。");
   }
} 

结果及分析

棋子车当前位置为:第1行第1列。
棋子车当前位置为:第1行第4列。
棋子车当前位置为:第5行第4列。
******悔棋******
棋子车当前位置为:第1行第4列。
******悔棋******
棋子车当前位置为:第1行第1列。
******撤销悔棋******
棋子车当前位置为:第1行第4列。
******撤销悔棋******
棋子车当前位置为:第5行第4列。

实现符合单一职责原则:

  • 原发类只关心内部状态
  • Memento只存储状态
  • Caretaker管理备忘录
  • Client实现撤销和前进行为

实例类介绍

(1) Chessman:象棋棋子类,充当原发器
(2) ChessmanMemento:象棋棋子备忘录类,充当备忘录
(3) MementoCaretaker:象棋棋子备忘录管理类,充当负责人
(4) Client:客户端测试类

备忘录类定义位置

定义在原发器的内部

将备忘录作为内部类

内部定义备忘录的优点:

  1. 设计和实现比较简单。
  2. 性能高效,仅需要保存一个状态。

缺点:

  1. 只能保存单次记录,不能管理多个备忘录。
  2. 职责不清晰,原发器对象管理其自身状态。
  3. 如果需要共享备忘录,则实现复杂。

定义在外部

比如我们的实例代码就是写在原发器外面,原发器里只需要定义备忘录创建和备忘录读取的方法(因为我们恢复状态,恢复的是原发器的状态,所以需要从备忘录读取状态赋值给当前原发器)。

外部定义备忘录的优点:

  1. 更灵活,可以保存多个状态。可以使用集合等结构来管理多个备忘录对象。
  2. 可以伴随其它对象或结构一起管理。
  3. 可以在多个原发器对象之间共享备忘录。
  4. 符合单一职责原则,将状态保存和管理分离。

缺点:

  1. 设计和实现复杂度较高,需要额外的管理类。
  2. 会有额外的性能开销,需要创建和管理多个备忘录对象。

模式优缺点

模式优点

  • 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
  • 实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动

模式缺点

  • 资源消耗过大。如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源

适用环境

  • 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时能够恢复到先前的状态,实现撤销操作
  • 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYzdW66Y' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片