备忘录模式
意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
使用场景
在以下情况下使用备忘录模式:
- 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
- 支持取消操作。
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
结构
备忘录模式结构如下
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会对它的状态进行赋值和检索。
时序图如下所示
效果
保持封装边界
– 使用备忘录可以避免暴露一些只应由Originator管理却又必须存储在Originator之外的信息。该模式把可能很复杂的Originator内部信息对其他对象屏蔽起来, 从而保持了封装边界。它简化了Originator
– 在其他的保持封装性的设计中, Originator负责保持客户请求过的内部状态版本。这就把所有存储管理的重任交给了Originator。让客户管理它们请求的状态将会简化Originator, 并且使得客户工作结束时无需通知原发器。使用备忘录可能代价很高
– 如果原发器在生成备忘录时必须拷贝并存储大量的信息, 或者客户非常频繁地创建备忘录和恢复Originator状态,可能会导致非常大的开销。除非封装和恢复Originator状态的开销不大, 否则该模式可能并不合适。定义窄接口和宽接口
– 在一些语言中可能难以保证只有原发器可访问备忘录的状态。维护备忘录的潜在代价
– 管理器负责删除它所维护的备忘录。然而, 管理器不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理器,可能会产生大量的存储开销。
备忘录模式实现(Implement)
案例
国际象棋是世界上最流行的游戏之一,棋盘由8乘8的网格组成。现在设计一款国际象棋游戏,要求这个游戏能够有悔棋的功能,这里可以用备忘录模式实现。
代码实现
首先定义国际象棋游戏,ChessGame类,对应模式中的Originator
ChessGame类中包含的棋盘棋局类ChessBoard
对棋局可以备份的ChessGameMemento类
对备份进行管理的ChessGameCaretaker类
总结
备忘录模式可以还原对象的状态,并且使得备份的实现结构简化,但同时有着消耗资源的缺点