设计模式学习 - 备忘录模式

在软件使用过程中用户难免会出现一些误操作,例如不小心删除了某些文字或图片,对于这些误操作需要提供一种“后悔药”的机制,实现撤销操作,例如我们常用Ctrl + Z。

备忘录模式正式为了解决此类撤销问题而诞生的,它为软件提供了“后悔药”,通过使用备忘录模式可以让系统恢复到某一特定的历史状态。

定义

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

结构

Originator(原发器)

原发器是一个普通类,它通过创建一个备忘录来存储当前内部状态,也可以使用备忘录来恢复其内部状态,一般将系统中需要保存内部状态的类设计为原发器。

Memento(备忘录)

备忘录用于存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接提供其他类使用,原发器的设计在不同的编程语言中谁先机制会有所不同。

Caretaker(负责人)

负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

举例说明

象棋的悔棋功能。

1.原发器 - 象棋棋子类

// 原发器 - 象棋棋子
@Setter@Getter
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 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();
	}
}

2.备忘录

// 备忘录 - 象棋棋子备忘录类
@Getter@Setter
public 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;
	}
	
}

3.负责人/管理者

// 负责人 - 象棋棋子备忘录管理类
@Setter@Getter
public class MementoCaretaker {
	private ChessmanMemento memento;
}

4.测试

public class Client {
	public static void main(String[] args) {
		// 创建负责人类 - 用于存储备忘录类
		MementoCaretaker mementoCaretaker = new MementoCaretaker();
		// 设置棋子初始位置及棋名
		Chessman chessman = new Chessman("车", 1, 1);
		display(chessman);
		
		mementoCaretaker.setMemento(chessman.save()); // 保存状态
		chessman.setY(4);
		display(chessman);
		mementoCaretaker.setMemento(chessman.save()); // 保存状态
		chessman.setY(6);
		display(chessman);
	
		System.out.println("******悔棋******");
		chessman.restore(mementoCaretaker.getMemento());
		display(chessman);
	}
	
	public static void display(Chessman chessman) {
		System.out.println("棋子 " + chessman.getLabel() + " 当前位置为(" + 
				 chessman.getX() + "," + chessman.getY() + ")"
		);
	}
}

5.运行截图

本例中,只保存了上一次的棋子状态,因此只能悔棋一次。

实现多次撤销

在负责人类中定义一个集合来存储多个备忘录,每个备忘录负责保存一个历史状态,在撤销时可以对备忘录集合进行逆向遍历,回到一个指定的历史装填,而且还可以对备忘录集合进行正向遍历,实现重做/恢复操作,即取消撤销,让对象状态得到恢复。

1.修改负责人类

// 负责人 - 象棋棋子备忘录管理类
public class MementoCaretaker {
	// 存储历史状态
	private List<ChessmanMemento> mementolist = new ArrayList<ChessmanMemento>();
	
	public ChessmanMemento getMemento(int i) {
		return (ChessmanMemento) mementolist.get(i);
	}
	
	public void setMemento(ChessmanMemento chessmanMemento) {
		mementolist.add(chessmanMemento);
	}
}

2.修改客户端

public class Client {
	// 索引 - 用来记录当前状态所在的位置
	private static int index = -1;
	private static MementoCaretaker mc = new MementoCaretaker();
	
	public static void main(String[] args) {
		Chessman chessman = new Chessman("车", 1, 1);
		play(chessman);
		chessman.setY(2);
		play(chessman);
		chessman.setY(3);
		play(chessman);
		
		undo(chessman, index);
		undo(chessman, index);
		redo(chessman, index);
		redo(chessman, index);
	}
	
	// 下棋
	public static void play(Chessman chessman) {
		// 保存备忘录
		mc.setMemento(chessman.save());
		index++;
		System.out.println("棋子 " + chessman.getLabel() + " 当前位置为(" + 
				 chessman.getX() + "," + chessman.getY() + ")"
		);
	}
	
	// 悔棋
	public static void undo(Chessman chessman, int i) {
		System.out.println("******悔棋******");
		index --;
		// 撤销到上一个备忘录
		chessman.restore(mc.getMemento(i - 1));
		System.out.println("棋子 " + chessman.getLabel() + " 当前位置为(" + 
				 chessman.getX() + "," + chessman.getY() + ")"
		);
	}
	
	// 撤销悔棋
	public static void redo(Chessman chessman,int i) {
		System.out.println("****撤销悔棋****");
		index ++;
		// 恢复到下一个备忘录
		chessman.restore(mc.getMemento(i + 1));
		System.out.println("棋子 " + chessman.getLabel() + " 当前位置为(" + 
				 chessman.getX() + "," + chessman.getY() + ")"
		);
	}
}

3.运行截图

本例只实现了最简单的撤销和恢复操作,并未考虑对象状态在操作过程中出现分支的情况。如果在撤销到某个历史状态之后用户再修改对象状态,此后执行撤销操作时可能会发生对象状态错误。在实际开发中可以使用链表或者堆栈来处理由分支的对象状态改变。

优点

1.提供了一种状态恢复的实现机制,使得用户可以方便地回到某一特定的历史步骤。

2.实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点

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

发布了59 篇原创文章 · 获赞 13 · 访问量 2515

猜你喜欢

转载自blog.csdn.net/qq_40885085/article/details/103483505