浅谈设计模式 - 备忘录模式
前言
备忘录这个模式用的比较少,基本学完就可以忘记的一个模式,但是应用的情况还是不少,同时这个模式几乎“人手必备”,那就是典型的ctrl+z这个功能就可以看作备忘录的典型案例,我们的游戏存档也可以看作是一种备忘录的变形。
定义
备忘录模式重点在于记录住对象的状态,可以让对象的状态回到上一次操作的时候,同时可以保证不破坏封装性的前提下存在于其他的对象,这个模式需要注意的一个点是为了管理备忘的类,需要一个“辅助类”来进行备忘内容的统一管理,就好比我们的游戏的存档和读档一样。
应用场景
- 游戏的存档
- 恢复上一次状态
- 文字撤销
- 还原
优缺点:
- 不破坏对象的封装性保存对象的状态,可以让对象回到过去定义的任何一种状态。
- 用户不需要关系备忘的内容以及如何备忘,只需要关注备忘的数据即可。
- 备忘的数据过多会造成卡顿,并且有可能存在备忘记录丢失的可能性。
结构图:
备忘录模式主要分为三个点,第一:需要定义和备忘的内容有关的对外接口,并且将请求转发给具体的备忘器,然后备忘器备忘数据存储到具体的备忘实现对象来保存对象的状态,第二:定义备忘录存储接口传递对象的状态,记录当前的工作数据,第三需要使用具体的记录器恢复器对象来恢复具体的对象数据。
了解基本的设计对象之后下面来看下具体的实现结构图,这种模式有两种实现的结构方式,一种是嵌套类的结构,另一种是使用中间接口委托的形式进行处理,但是不管是嵌套结构还是中间接口委托的形式,基本都包含了下面的接口:
Memento(备忘录):包含了要被恢复的对象的状态,是快照的具体存储位置。
Originator(原发器):创建并在 Memento 对象中存储状态,可以看作是需要被快照的对象,通常包含某些状态数据。
Caretake(恢复器):负责从 Memento 中恢复对象的状态,可以看作是一个记录提取器或者说恢复器。
嵌套结构
嵌套结构的结构图如下所示,嵌套的结构下通常会把原发器嵌入到备忘录的接口里面,在部分的时候通过备忘录把数据进行恢复或者存储,恢复器负责对于原发器的状态进行还原的操作:
中间接口结构
中间接口结构针对一些不能使用嵌套类的情况,这时候我们只能委托给子类完成备份数据的处理,当然这样也有一种灵活性,就是备份的方式可以多样化。
案例代码
下面我们来看下上面的结构图的相关代码,由于这里使用的是java代码,所以我们直接使用嵌套类的形式完成:
Originator(原发器):创建并在 Memento 对象中存储状态,可以看作是需要被快照的对象,通常包含某些状态数据。
public class Originator { private String state; public Originator() { } public Originator(Memento memento) { memento = new Memento(state); } public String getState() { return state; } public void setState(String state) { this.state = state; } }
Memento(备忘录):包含了要被恢复的对象的状态,是快照的具体存储位置。
public class Memento { /** * 需要被备份的状态 */ private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } }
Caretake(恢复器):负责从 Memento 中恢复对象的状态,可以看作是一个记录提取器或者说恢复器。
public class CareTaker { private Memento memento; public CareTaker(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
Client:最终的执行客户端,通过调用CareTaker进行将数据的状态进行还原:
public class Client { private Originator originator; public Client(Originator originator) { this.originator = originator; } /** * 恢复记录的状态 */ public void restore(CareTaker careTaker) { originator.setState(Optional.ofNullable(careTaker) .map(CareTaker::getMemento) .map(Memento::getState) .orElse("off")); } }
最后是单元测试:
public class Main { public static void main(String[] args) { Originator originator = new Originator(); originator.setState("on"); System.out.println("备份状态"+ originator.getState()); Memento memento = new Memento(originator.getState()); CareTaker careTaker = new CareTaker(memento); //修改状态 originator.setState("test"); System.out.println("修改状态"+ originator.getState()); Client client = new Client(originator); client.restore(careTaker); System.out.println("还原状态"+ originator.getState()); }/*运行结果: 备份状态on 修改状态test 还原状态on */ }
实际案例
不管是操作系统还是文本编辑,备忘和撤销的功能是必不可少的,所以我们用文本编辑器来讲述备忘录模式。最常见的情况是我们写好文章写到一半的时候关闭软件下次进入依然有原来的数据。这里我们使用“特殊方法”代替ctrl+s来进行数据的备份操作。
代码的内容和案例内容类似,只不过由于使用了对象需要注意对象的潜拷贝和深拷贝的问题。
首先我们建立文本对象:
public class Text { /** * 文本信息 */ private String info; public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } }
下面是一个文本编辑器:
public class TextEditor { private Text text; public TextEditor(Text text) { this.text = text; } public void write(String info){ text.setInfo(info); } public Text getText() { return text; } public void setText(Text text) { this.text = text; } }
备忘录需要注意备份对象需要使用深拷贝
public class Memento { private Text text; public Text getText() { return text; } public void setText(Text text) { String info = text.getInfo(); Text newText = new Text(); newText.setInfo(info); this.text = newText; } }
恢复器,最后要使用恢复器进行数据的恢复操作
public class CareTaker { private Memento memento; public CareTaker(Memento memento) { this.memento = memento; } /** * 恢复记录的状态 */ public void restore(TextEditor textEditor) { textEditor.getText().setInfo(memento.getText().getInfo()); } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
最后是测试代码:
public class Main { public static void main(String[] args) { Text text = new Text(); TextEditor textEditor = new TextEditor(text); textEditor.write("d111111"); Memento memento = new Memento(); memento.setText(text); CareTaker careTaker = new CareTaker(memento); textEditor.write("66666"); careTaker.restore(textEditor); System.out.println(careTaker.getMemento().getText().getInfo()); }/*运行结果: 浅拷贝:66666 深拷贝:d111111 */ }
总结
这个设计模式用的真的并不算很多,所以简单过一下即可。
写在最后
算是比较有意思的设计模式。