设计模式(19):备忘录模式MEMENTO

备忘录模式

意图

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


使用场景

在以下情况下使用备忘录模式:

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
  • 支持取消操作。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

结构

备忘录模式结构如下
ShowImage

  • Memento(备忘录)

    – Memento存储Originator对象的内部状态。Originator根据需要决定Memento存储Originator的哪些内部状态。

    – 防止Originator以外的其他对象访问Memento。Memento实际上有两个接口,管理者(caretaker)只能看到Memento的窄接口—它只能将Memento传递给其他对象。相反, Originator能够看到一个宽接口, 允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本Memento的那个原发器访问本Memento的内部状态。

  • Originator(原发器)

    – Originator创建一个Memento,用以记录当前时刻它的内部状态。

    – 使用Memento恢复内部状态。

  • Caretaker(管理器)

    – 负责保存好Memento。

    – 不能对Memento的内容进行操作或检查。

协作

  • Caretaker向Originator请求一个Memento,保留一段时间后,将其送回给Originator。有时Caretaker不会将Memento返回给Originator, 因为Originator可能根本不需要退到先前的状态。
  • Memento是被动的。只有创建Memento的Originator会对它的状态进行赋值和检索。

时序图如下所示
ShowImage


效果

  • 保持封装边界

    – 使用备忘录可以避免暴露一些只应由Originator管理却又必须存储在Originator之外的信息。该模式把可能很复杂的Originator内部信息对其他对象屏蔽起来, 从而保持了封装边界。

  • 它简化了Originator

    – 在其他的保持封装性的设计中, Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的重任交给了Originator。让客户管理它们请求的状态将会简化Originator, 并且使得客户工作结束时无需通知原发器。

  • 使用备忘录可能代价很高

    – 如果原发器在生成备忘录时必须拷贝并存储大量的信息, 或者客户非常频繁地创建备忘录和恢复Originator状态,可能会导致非常大的开销。除非封装和恢复Originator状态的开销不大, 否则该模式可能并不合适。

  • 定义窄接口和宽接口

    – 在一些语言中可能难以保证只有原发器可访问备忘录的状态。

  • 维护备忘录的潜在代价

    – 管理器负责删除它所维护的备忘录。然而, 管理器不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理器,可能会产生大量的存储开销。

备忘录模式实现(Implement)

案例

国际象棋是世界上最流行的游戏之一,棋盘由8乘8的网格组成。现在设计一款国际象棋游戏,要求这个游戏能够有悔棋的功能,这里可以用备忘录模式实现。

代码实现

首先定义国际象棋游戏,ChessGame类,对应模式中的Originator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* Chess Game, Originator in pattern */
public class ChessGame {
private ChessBoard chessBoard;
public ChessGame() {
//初始化棋盘
setChessBoard(new ChessBoard());
}
public ChessBoard getChessBoard() {
return chessBoard;
}
public void setChessBoard(ChessBoard chessBoard) {
this.chessBoard = chessBoard;
}
//保存比赛
public ChessGameMemento saveGame() throws CloneNotSupportedException {
return new ChessGameMemento(chessBoard.clone());
}
//恢复比赛
public void restoreGame(ChessGameMemento chessGameMemento) {
this.chessBoard = chessGameMemento.getChessBoard();
}
//显示棋局
public void showGameDetails() {
this.getChessBoard().showLayout();
}
}

ChessGame类中包含的棋盘棋局类ChessBoard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* 国际象棋中棋子的棋局*/
public class ChessBoard implements Cloneable {
//代码太多进行省略,只显示王的位置
private int rowOfBlackKing;
private char columnOfBlackKing;
private int rowOfWhiteKing;
private char columnOfWhiteKing;
//初始化各个棋子的位置
public ChessBoard() {
this.setColumnOfBlackKing('e');
this.setRowOfBlackKing(8);
this.setColumnOfWhiteKing('e');
this.setRowOfWhiteKing(1);
}
public void showLayout() {
System.out.println("当前的棋局[" + "黑棋的王的位置 : " + getColumnOfBlackKing() + getRowOfBlackKing() + ",白棋的王的位置 : " + getColumnOfWhiteKing() + getRowOfWhiteKing() + "]");
}
//省略getter and setter...
@Override
protected ChessBoard clone() throws CloneNotSupportedException {
return (ChessBoard) super.clone();
}
}

对棋局可以备份的ChessGameMemento类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Memento in pattern */
public class ChessGameMemento {
private ChessBoard chessBoard;
public ChessGameMemento(ChessBoard chessBoard) {
this.chessBoard = chessBoard;
}
public ChessBoard getChessBoard() {
return chessBoard;
}
public void setChessBoard(ChessBoard chessBoard) {
this.chessBoard = chessBoard;
}
}

对备份进行管理的ChessGameCaretaker类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Caretaker in pattern */
public class ChessGameCaretaker {
private List<ChessGameMemento> mementoList = new ArrayList<>();
//step表示第几步
public void addMemento(int step, ChessGameMemento chessGameMemento) {
mementoList.add(step, chessGameMemento);
}
public ChessGameMemento getMemento(int i) {
return mementoList.get(i);
}
}

总结

备忘录模式可以还原对象的状态,并且使得备份的实现结构简化,但同时有着消耗资源的缺点

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!