本篇讲解Java设计模式中的备忘录模式,分为定义、模式应用前案例、结构、模式应用后案例、适用场景、模式可能存在的困惑和本质探讨7个部分。
备忘录模式是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到保存的状态。
在新的分类方式中,备忘录模式被划分至类属性相关需求类别中,其应对的是类的状态属性需要恢复的要求。
文本编辑器是一个备忘录模式的典型应用场景。接下来,先来看一下未使用备忘录模式之前的代码实现。
public class TextEditor {//编辑器类-直接实现保存和恢复操作private String content;private String previousContent;public void write(String text) {if(this.content == null ) {this.content = ""; }this.content += text; }// 保存当前内容为上一个版本的状态public void save() {this.previousContent = this.content; }// 恢复到上一个版本的状态public void undo(){if(this.previousContent != null){this.content = this.previousContent; } }// 获取内容public String getContent(){return this.content; }}public class Client {//调用方代码public static void main(String[] ars){ TextEditor editor=new TextEditor(); editor.write("Hello, "); System.out.println(editor.getContent()); editor.save(); editor.write("World!"); System.out.println(editor.getContent()); editor.undo(); System.out.println(editor.getContent()); }}
在上述代码中,主要问题出现在TextEditor类中。为了实现恢复到上一步这个操作,在类中增加了previousContent属性。
如果这个功能是后来才需要增加的,则违背了OCP开闭原则。此外,如果后续要增加恢复上两步的操作,是否还要新增一个doublepreviousContent属性。显然,对于这种类状态(或属性)有变化且能够恢复的场景,应该有更好的解决方案。
备忘录模式的示例实现代码如下。
public class Originator {private String state;public Memento createMemento() {return new Memento(state); }public void setMemento(Memento memento) {this.state = ((Memento) memento).getState(); }public String getState() {return state; }public void setState(String state) {this.state = state; }}public class Memento{private final String state;public Memento(String state) {this.state = state; }public String getState() {return state; }}public class Caretaker {private Memento memento;public void setMemento(Memento memento) {this.memento = memento; }public Memento getMemento() {return memento; }}public class Client {public static void main(String[] args) {// 创建Originator对象 Originator originator = new Originator();// 设置初始状态 originator.setState("State 1"); System.out.println("Initial State: " + originator.getState());// 创建Caretaker对象并保存备忘录 Caretaker caretaker = new Caretaker(); caretaker.setMemento(originator.createMemento());// 改变Originator的状态 originator.setState("State 2"); System.out.println("State after change: " + originator.getState());// 恢复到之前保存的状态 originator.setMemento(caretaker.getMemento()); System.out.println("State after restore: " + originator.getState()); }}
从备忘录模式的结构和示例代码中,可以看到原有类Originator仅保留了与自身核心业务功能相关的属性,并将其需要恢复状态的属性state放在一个Memento类中保存。
Originator增加了两个比较简洁的方法,一个是创建Memento,一个是从Memento中恢复,所以setMemento方法使用restoreFromMemento会更加准确。
同时,增加了一个Caretaker类,它用于保存、恢复Memento。是恢复到上一个状态还是上两个状态都由Caretaker类专门负责。
不难发现,在备忘录模式下,各个类职责分工明确,核心类Originator专注于核心业务功能,Memento和Caretaker两个支撑类则用于实现状态的保存和恢复。
上面文本编辑器的案例,在应用备忘录模式之后的代码实现如下。
TextEditor类删掉了PreviousContent属性,职责更加单一。
public class TextEditor {// 编辑器类(Originator)- 负责创建备忘录和恢复到之前状态private String content;public void write(String text) {if(this.content == null) {this.content = ""; }this.content += text; }// 创建当前内容对应的备份public EditorMemento createMemento(){return new EditorMemento(this.content); }// 从传入Mememtor对象中获取内容并进行还原public void restoreFromMemento(EditorMemento memento){this.content = memento.getContent(); }public String getContent() {return this.content; }}
增加EditorMemento和UndoManager两个类,分别实现TextEditor中Content属性的保存,以及EditorMemento的管理。
public class EditorMemento {// 备忘录类(Memento)- 存储文本编辑器的状态private final String content;public EditorMemento(String content) {this.content = content; }public String getContent() {return this.content; }}public class UndoManager {// 管理者类(Caretaker)-负责管理保存和恢复操作 Stack<EditorMemento> emStack =new Stack<>();public void save(EditorMemento memento){this.emStack.push(memento); }public EditorMemento undo(){if(!this.emStack.empty()){return this.emStack.pop(); }return null; }}
最后,调用方代码如下。
public class Client {//调用方代码public static void main(String[] ars){ TextEditor editor = new TextEditor(); UndoManager undoManager=new UndoManager(); editor.write("Hello, "); undoManager.save(editor.createMemento()); editor.write("World!");//undoManager.save(editor.createMemento()); System.out.println(editor.getContent()); editor.restoreFromMemento(undoManager.undo()); System.out.println(editor.getContent()); }}
备忘录模式适用的场景非常明确,就是原有类在生命周期变化过程中,其属性的状态还可能需要恢复的场景。
困惑1:为什么要有Caretaker类,为什么不能在Memento或Originator中实现保存和恢复功能,这样程序更加简洁?
如果在Originator中实现,又违背了SRP单一职责和OCP开闭原则;如果在Memento实现,这个类功能会变多,每次在Originator中创建Memento对象会占用更多内存,从这个角度就不合适。
困惑2:Memento类只是一个数据的封装类,为什么Originator的状态属性不能直接放在Caretaker中通过一个数据属性来实现?
实际上,许多人在考虑状态恢复的策略时,通常会优先想到这个方案。为了更好地进行说明,这里将代码实现罗列出来。
public class TextEditor {// 编辑器类(Originator)- 负责创建备忘录和恢复到之前状态private String content;public void write(String text) {if(this.content == null) {this.content = ""; }this.content += text; }// 创建当前内容对应的备份public void saveContent(){ UndoManager.save(this.content); }// 获取内容并进行还原public void restoreFromContent(){this.content = UndoManager.undo(); }public String getContent() {return this.content; }}public class UndoManager {// 管理者类(Caretaker)-负责管理保存和恢复操作private static final Stack<String> emStack =new Stack<>();public static void save(String content){ emStack.push(content); }public static String undo(){if(!emStack.empty()){return emStack.pop(); }return null; }}public class Client {//调用方代码public static void main(String[] ars){ TextEditor editor = new TextEditor(); editor.write("Hello, "); editor.saveContent(); editor.write("World!"); System.out.println(editor.getContent()); editor.restoreFromContent(); System.out.println(editor.getContent()); }}
这种方式下,似乎实现起来更加简洁清晰。然而,缺点也比较明显。TextEditor与UndoManager紧耦合的情况下,如果TextEditor要求也能够实现恢复到前两个状态,此时UndoManager增加了一个undo2的方法,那么TextEditor也需要一并修改。
但是在备忘录模式下,TextEditor相当于至于纯数据类Memento进行交互,面对上面的需求并不需要修改,只需要将上两个的Memento传参即可。
困惑3:在关于备忘录模式的一些材料中,会看到宽接口和窄接口,具体是什么含义?
宽接口指的是Memento备忘录对象提供给Originator访问其内部状态的全部信息,包括私有数据。因为Memento里的数据其实就是Originator中要保存、恢复状态的数据,因此Originator需要能访问到具体的数据信息才可以。
窄接口指的是Memento备忘录对象对Caretaker对象指提供必要的信息进行访问和恢复操作。因为Caretaker对象需要是是Memento对象自身,并不需要访问Memento中的数据,因此称之为窄接口。
困惑4:备忘录模式实现之后,对于调用方的交互似乎变得更加复杂?
一件事情往往有得必有失,很难做到两全其美。为了使得Originator不违背SRP单一职责和OCP开闭原则,Client只能增加交互。
如果在Client和备忘录模式的类之间增加一个中间代理类,这样可以减少与调用方之间的交互,但是代价是又新增一个支撑类。
面向对象程序中,一个类在生命周期过程中,其属性构成的状态是会不断变化的。这种变化会带来很多不确定性,尤其在多线程场景下,可能也会引发一些意想不到的问题。因此,Java语言中经常提倡要利用不变性、局部变量等应对这种不确定性。
然而,在某些现实场景下,类随着时间不断变化是有必要的,并且要求还能沿着时间向后回退。此时,备忘录提供了一种管理对象状态的机制,并且让原有对象维持良好的封装性。
本文链接:http://www.28at.com/showinfo-26-88318-0.html一文彻底搞明白备忘录模式
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 你不知道的JavaScript—探索 JavaScript 对象与原型
下一篇: Redis Zset详解:排行榜绝佳选择