500强企业网站建设,云南app制作,佛山网络推广,企业销售网站建设备忘录模式 文章目录 备忘录模式什么是备忘录模式为什么要用备忘录模式如何使用备忘录模式总结 什么是备忘录模式 在不违背封装原则的前提下#xff0c;捕获一个对象的内部状态#xff0c;并在该对象之外保存这个状态#xff0c;以便之后恢复对象为先前的状态。 在我看来…备忘录模式 文章目录 备忘录模式什么是备忘录模式为什么要用备忘录模式如何使用备忘录模式总结 什么是备忘录模式 在不违背封装原则的前提下捕获一个对象的内部状态并在该对象之外保存这个状态以便之后恢复对象为先前的状态。 在我看来这个模式的定义主要表达了两部分内容。一部分是存储副本以便后期恢复。这一部分很好理解。另一部分是要在不违背封装原则的前提下进行对象的备份和恢复。
为什么要用备忘录模式 接下来我就结合一个例子来解释一下特别带你搞清楚这两个问题
为什么存储和恢复副本会违背封装原则备忘录模式是如何做到不违背封装原则的 假设有这样一道面试题希望你编写一个小程序可以接收命令行的输入。用户输入文本时程序将其追加存储在内存文本中用户输入“:list”程序在命令行中输出内存文本的内容用户输入“:undo”程序会撤销上一次输入的文本也就是从内存文本中将上次输入的文本删除掉。 我举了个小例子来解释一下这个需求如下所示
hello
:list
hello
world
:list
helloworld
:undo
:list
hello 怎么来编程实现呢你可以打开 IDE 自己先试着编写一下然后再看我下面的讲解。整体上来讲这个小程序实现起来并不复杂。我写了一种实现思路如下所示
public class InputText {private StringBuilder text new StringBuilder();public String getText() {return text.toString();}public void append(String input) {text.append(input);}public void setText(String text) {this.text.replace(0, this.text.length(), text);}
}public class SnapshotHolder {private StackInputText snapshots new Stack();public InputText popSnapshot() {return snapshots.pop();}public void pushSnapshot(InputText inputText) {InputText deepClonedInputText new InputText();deepClonedInputText.setText(inputText.getText());snapshots.push(deepClonedInputText);}
}public class ApplicationMain {public static void main(String[] args) {InputText inputText new InputText();SnapshotHolder snapshotsHolder new SnapshotHolder();Scanner scanner new Scanner(System.in);while (scanner.hasNext()) {String input scanner.next();if (input.equals(:list)) {System.out.println(inputText.getText());} else if (input.equals(:undo)) {InputText snapshot snapshotsHolder.popSnapshot();inputText.setText(snapshot.getText());} else {snapshotsHolder.pushSnapshot(inputText);inputText.append(input);}}}
} 实际上备忘录模式的实现很灵活也没有很固定的实现方式在不同的业务需求、不同编程语言下代码实现可能都不大一样。上面的代码基本上已经实现了最基本的备忘录的功能。但是如果我们深究一下的话还有一些问题要解决那就是前面定义中提到的第二点要在不违背封装原则的前提下进行对象的备份和恢复。而上面的代码并不满足这一点主要体现在下面两方面
第一为了能用快照恢复 InputText 对象我们在 InputText 类中定义了 setText() 函数但这个函数有可能会被其他业务使用所以暴露不应该暴露的函数违背了封装原则第二快照本身是不可变的理论上讲不应该包含任何 set() 等修改内部状态的函数但在上面的代码实现中“快照“这个业务模型复用了 InputText 类的定义而 InputText 类本身有一系列修改内部状态的函数所以用 InputText 类来表示快照违背了封装原则。
如何使用备忘录模式 针对以上问题我们对代码做两点修改。其一定义一个独立的类Snapshot 类来表示快照而不是复用 InputText 类。这个类只暴露 get() 方法没有 set() 等任何修改内部状态的方法。其二在 InputText 类中我们把 setText() 方法重命名为 restoreSnapshot() 方法用意更加明确只用来恢复对象。 按照这个思路我们对代码进行重构。重构之后的代码如下所示
public class InputText {private StringBuilder text new StringBuilder();public String getText() {return text.toString();}public void append(String input) {text.append(input);}public Snapshot createSnapshot() {return new Snapshot(text.toString());}public void restoreSnapshot(Snapshot snapshot) {this.text.replace(0, this.text.length(), snapshot.getText());}
}public class Snapshot {private String text;public Snapshot(String text) {this.text text;}public String getText() {return this.text;}
}public class SnapshotHolder {private StackSnapshot snapshots new Stack();public Snapshot popSnapshot() {return snapshots.pop();}public void pushSnapshot(Snapshot snapshot) {snapshots.push(snapshot);}
}public class ApplicationMain {public static void main(String[] args) {InputText inputText new InputText();SnapshotHolder snapshotsHolder new SnapshotHolder();Scanner scanner new Scanner(System.in);while (scanner.hasNext()) {String input scanner.next();if (input.equals(:list)) {System.out.println(inputText.toString());} else if (input.equals(:undo)) {Snapshot snapshot snapshotsHolder.popSnapshot();inputText.restoreSnapshot(snapshot);} else {snapshotsHolder.pushSnapshot(inputText.createSnapshot());inputText.append(input);}}}
}
总结 前面我们只是简单介绍了备忘录模式的原理和经典实现现在我们再继续深挖一下。如果要备份的对象数据比较大备份频率又比较高那快照占用的内存会比较大备份和恢复的耗时会比较长。这个问题该如何解决呢 不同的应用场景下有不同的解决方法。比如我们前面举的那个例子应用场景是利用备忘录来实现撤销操作而且仅仅支持顺序撤销也就是说每次操作只能撤销上一次的输入不能跳过上次输入撤销之前的输入。在具有这样特点的应用场景下为了节省内存我们不需要在快照中存储完整的文本只需要记录少许信息比如在获取快照当下的文本长度用这个值结合 InputText 类对象存储的文本来做撤销操作。 我们再举一个例子。假设每当有数据改动我们都需要生成一个备份以备之后恢复。如果需要备份的数据很大这样高频率的备份不管是对存储内存或者硬盘的消耗还是对时间的消耗都可能是无法接受的。想要解决这个问题我们一般会采用“低频率全量备份”和“高频率增量备份”相结合的方法。 全量备份就不用讲了它跟我们上面的例子类似就是把所有的数据“拍个快照”保存下来。所谓“增量备份”指的是记录每次操作或数据变动。 当我们需要恢复到某一时间点的备份的时候如果这一时间点有做全量备份我们直接拿来恢复就可以了。如果这一时间点没有对应的全量备份我们就先找到最近的一次全量备份然后用它来恢复之后执行此次全量备份跟这一时间点之间的所有增量备份也就是对应的操作或者数据变动。这样就能减少全量备份的数量和频率减少对时间、内存的消耗。