公元1722年,康熙皇帝驾崩于北京畅春园,步军统领隆科多取出了藏在正大光明匾额后面的遗诏,宣布四阿哥胤禛克承大统,继承皇位,天朝帝国从此走进了新时代。康熙为什么采用遗诏,而不是自己宣布继承人?还不是为了防止出现意外:躲猫猫、马航370、相亲遇到翟欣欣、被闺蜜锁在门外…;别人能够知道和篡改遗诏的内容吗?别人敢吗?!这样康熙使用遗诏的方式,在龙驭宾天后,恢复了“皇帝类”的另一个对象实例:雍正。康熙爷在300年前就给我们玩了一把备忘录模式。
备忘录是一个非常简单的模式,先看定义及结构图:
备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
Memeto角色(备忘录):保存Originator对象的内部状态,并且只能由Originator才能对它存取数据,它对其它对象是不可见的。“遗诏”就是这个角色,只有康熙知道,谁也不知道。
Originator角色(源发器):创建Memeto,把状态记录写入memeto,可以使用备忘录恢复内部状态。“皇帝类”就是这个角色,康熙和雍正分别是被销毁和被恢复的皇帝对象实例。
CareTaker角色(保管者):字面意思是小心的看管者,负责保存好备忘录,不能对备忘录的内容进行操作或检查。“隆科多”就是这个角色,看管遗诏还不得小心翼翼,还敢偷看和修改,他有几个脑袋?
分析说明:
1. 我们知道在Java中有clone操作,在C++中有拷贝构造函数,都可以创建一个完全相同的对象实例,那么我们是否可以使用它们来复制一个完全一模一样的Originator对象,作为它的副本呢?这样当需要恢复它的时候,从副本中得到它的原始状态,或者干脆直接使用备份的Originator对象,不更简单直接吗?不一定可行。
首先,Originator的状态不一定需要全部备份,比如有的状态是暂态的,只与它运行的时刻有关,备份它们是没有意义的,还浪费存储空间,也就不需要保存的;
其次,有的Originator的状态可能是活动对象,比如一个文件对象File或者网络连接对象Socket,它们是无法保存的。就算是保存下来,再恢复时,已经脱离了它所运行上下文环境,也无法正常运行,备份时应该只保存用于创建它们的信息就行了。比如,对于File对象可以保存它的文件路径,对于Socket对象,保存的是IP地址和端口号。
最后,保存的状态有可能序列化,以传输到其它进程,甚至持久化到文件系统,如果备份整个Originator,数据量有可能很大,比如Serializable对象在序列化时会把所有祖先类的状态属性全部保存。
2. Originator自己负责保存状态行吗?不行,保存状态的目的是为了在某些场合恢复状态,如果Originator被杀死了,它所保存的状态,也就一块释放了,当然可以把状态持久化到磁盘上,但IO操作又是一个很大的开销,一般不使用。所以,只能保存在Originator的外部,比如让Caretaker来保存状态。
这样就会产生另一个问题,如果Originator把状态看作是自己的私有属性,不想暴露出来,那么Caretaker就无法得到数据,或者进一步说,就算Originator能把状态访问全部开放给Caretaker,比如C++中的友元类,这样既能封闭自己的状态,还能让友元类Caretaker来访问它的状态。那么随着业务的演化发展,Originator需要添加一个新的状态属性,怎样变化呢?首先要修改Originator,其次在保存状态时,要修改CareTaker,有两处变化,因此,需要封装CareTaker的变化。
3. 既然该模式中的发生变化的地方是Originator的状态,那就使用Memeto来封装它。从类结构图我们也能看到,把状态封装在了Memeto中,就能做到既封装状态属性,又能封装状态保存和恢复操作(Memeto参数类型不变,它里面属性的变化不影响保存和恢复操作)。虽然Memeto对象关联着Originator和Caretaker,但是它却是一个具体类,没有使用抽象类来实现依赖倒置,为什么呢?因为它是一个数据类对象,只是用来保存Originator的状态数据(在实现时,可能都是private的,比如作为Originator的内部类存在时,封装了状态属性),而且Caretaker也从不关心Memeto的具体细节,也不会访问Memeto对象,就算变化也是在Memeto内变化,影响不到Caretaker(封装了状态保存和恢复操作)。如果发生了变化,比如增加或删除状态,Originator要修改自己内部状态,同时修改Memeto的保存和恢复状态的方法,都是在Originator内部发生的,显然对Caretaker没有影响。
4.备忘录Memeto对象是被动的,它只对状态进行读写,没有其它业务逻辑,保管者Caretaker对象负责保存备忘录,从不过问备忘录细节,源发器Originator负责产生和使用备忘录。因此,一个对象的状态记录和保存记录被分开处理了。
使用场合:
对象既想让外部对象来保存它的状态,又不想破坏它的内部状态的封装。
实际应用:
Android中Activity和Fragment使用onSavedInstance()来保存状态,就是使用备忘录模式来实现。
我们知道Activity的生命周期被Andriod系统给托管了,当配置发生了变化,或者系统内存资源不够时,Android一般会杀死Activity,为了保证用户体验的一致性,当用户重新返回被系统杀死的那个Activity时,Android会重建那个Activity,并把Activity的状态恢复到被杀死时的状态,这样在用户看来界面没有发生变化。
为了达到这个目的,就需要在Activity被杀死时保存状态,那么状态保存到哪儿呢,显然是不可能保存在Activity中,因为它被杀死后,所引用的对象全部释放了,那就只能保存在外部了,使用备忘录模式再合适不过了。如果我们分析android的代码,Originator对应的就是Activity,Memento对应的就是Bundle,Caretaker就是ActivityManangerService了。
下面看一下android系统是如何使用备忘录模式的:
在Android中的Activity、Fragment、View、ViewPager等的状态保存就是典型的备忘录模式。它们都是使用Parcelable作为Memeto角色的,一般使用Bundle。还有一种更为简单的方式,用SavedState类扮演了Memeto角色,它也是Parcelable的实现类,但比Bundle更为节省空间,它是直接写到Parcel中,不像Bundle那样采用Key-Value的方式。
1. View:Window类是CareTaker角色。
2. Fragment:Caretaker角色是FragmentManager类。Fragment有两种状态,一个属性状态,savedState保存,另一个是FragmentState,由Activity管理。不过,它们并没有严格按照备忘录实现,在备份状态时,都向Fragmentmanager和Activity暴露了它的状态信息,也就是说Fragment并未向Caretaker完全封闭状态。这不算是一个最佳的方案,这点是和备忘录模式的目的相背离的,需要注意。
3. ViewPager:标准的备忘录模式,而且是按照SavedState实现的状态保存与恢复,大家可以分析一下它的状态保存的实现。
示例:
下面使用备忘录模式和命令模式实现文件操作的undo功能,基本思路是把文件操作对象中的文件路径作为备忘录保存在栈中,在undo的时候,从栈中一个一个的恢复,然后执行每个恢复的文件操作类对象的undo操作:
public class MemetoPattern {
// 命令接口类
interface ICommand {
void execute();
}
// 命令调用者,负责保存命令,并执行命令
static class Invoker {
private Stack<ICommand> mStack;//undo命令放在堆栈中,按照先进后出的顺序undo
public Invoker() {
mStack = new Stack();
}
public void putCommand(ICommand command) {//保存Undo命令
mStack.push(command);
}
public void run() {//执行undo操作
try {
ICommand command = mStack.pop();
command.execute();
} catch (EmptyStackException e) {
e.printStackTrace();
}
}
}
static abstract class OperateFile {
public abstract void operate(); // 文件操作方法
public abstract void undo(State state); // undo文件操作方法
public abstract State saveState(); // 保存状态
public static class State { // 文件操作的状态,也就是备忘录Memeto类,是内部类
protected String srcFile; // protected修饰,只有OperateFile才能访问
protected String dstFile;
}
}
// 拷贝文件的操作类
static class CopyFile extends OperateFile {
private String srcFile;
private String dstFile;
public CopyFile() {
}
public CopyFile(String srcFile, String dstFile) {
this.srcFile = srcFile;
this.dstFile = dstFile;
}
public void operate() {
System.out.println("copy:"+srcFile+" to "+dstFile);
}
public void undo(State state) { // undo操作相当于删除目的文件
DeleteFile operation = new DeleteFile(state.dstFile); //恢复状态
operation.operate();
}
public State saveState() {
State state = new State();
state.srcFile = srcFile;
state.dstFile = dstFile;
return state;
}
}
// 创建文件的操作类
static class CreateFile extends OperateFile {
private String newFile;
public CreateFile() {
}
public CreateFile(String newFile) {
this.newFile = newFile;
}
public void operate() {
System.out.println("create:"+newFile);
}
public void undo(State state) { // undo操作相当于删除文件
DeleteFile operation = new DeleteFile(state.srcFile);
operation.operate(); //恢复状态
}
public State saveState() {
State state = new State();
state.srcFile = newFile;
return state;
}
}
// 移动文件的操作类
static class MoveFile extends OperateFile {
private String srcFile;
private String dstFile;
public MoveFile(String srcFile, String dstFile) {
this.srcFile = srcFile;
this.dstFile = dstFile;
}
public MoveFile() {
}
public void operate() {
System.out.println("move:"+srcFile+" to "+dstFile);
}
public void undo(State state) { //undo操作相当于反方向移动
this.srcFile = state.dstFile; //恢复状态
this.dstFile = state.srcFile;
operate();
}
public State saveState() {
State state = new State();
state.srcFile = srcFile;
state.dstFile = dstFile;
return state;
}
}
// 删除文件的操作类
static class DeleteFile extends OperateFile {
private String srcFile;
private String dstFile;
public DeleteFile() {
}
public DeleteFile(String file) {
this.srcFile = file;
}
public void operate() {
System.out.println("delete:"+srcFile);
dstFile = "/recycle"+srcFile;
}
public void undo(State state) {// undo操作相当于从回收站移动回来
MoveFile move = new MoveFile(state.srcFile, state.dstFile); //恢复状态
move.operate();
}
public State saveState() {
State state = new State();
state.srcFile = srcFile;
state.dstFile = dstFile;
return state;
}
}
// 创建文件的undo命令类,同时也是Caretaker类
static class UnCreateFileCommand implements ICommand {
OperateFile.State mState; //保存状态
public UnCreateFileCommand(OperateFile.State state) {
mState = state;
}
@Override
public void execute() {
CreateFile create = new CreateFile(); //根据状态恢复CreateFile对象
create.undo(mState);
}
}
// 删除文件的undo命令类,同时也是Caretaker类
static class UnDeleteFileCommand implements ICommand {
OperateFile.State mState; //保存状态
public UnDeleteFileCommand(OperateFile.State state) {
mState = state;
}
@Override
public void execute() {
DeleteFile delete = new DeleteFile(); //根据状态恢复DeleteFile对象
delete.undo(mState);
}
}
// 移动文件的undo命令类,同时也是Caretaker类
static class UnMoveFileCommand implements ICommand {
OperateFile.State mState; //保存状态
public UnMoveFileCommand(OperateFile.State state) {
mState = state;
}
@Override
public void execute() {
MoveFile create = new MoveFile(); //根据状态恢复MoveFile对象
create.undo(mState);
}
}
// 拷贝文件的undo命令类,同时也是Caretaker类
static class UnCopyFileCommand implements ICommand {
OperateFile.State mState; //保存状态
public UnCopyFileCommand(OperateFile.State state) {
mState = state;
}
@Override
public void execute() {
CopyFile copy = new CopyFile(); //根据状态恢复CopyFile对象
copy.undo(mState);
}
}
// 文件操作类,命令的客户端Client
static class FileOperation {
private Invoker mInvoker;
public FileOperation() {
mInvoker = new Invoker();
}
// 创建文件成功后,创建它的undo命令,并保存起来
public void create(String file) {
CreateFile createFile = new CreateFile(file);
createFile.operate();
mInvoker.putCommand(new UnCreateFileCommand(createFile.saveState()));
}
// 拷贝文件成功后,创建它的undo命令,并保存起来
public void copy(String src, String dst) {
CopyFile copyFile = new CopyFile(src, dst);
copyFile.operate();
mInvoker.putCommand(new UnCopyFileCommand(copyFile.saveState()));
}
// 移动文件成功后,创建它的undo命令,并保存起来
public void move(String src, String dst) {
MoveFile moveFile = new MoveFile(src, dst);
moveFile.operate();
mInvoker.putCommand(new UnMoveFileCommand(moveFile.saveState()));
}
// 删除文件成功后,创建它的undo命令,并保存起来
public void delete(String file) {
DeleteFile deleteFile = new DeleteFile(file);
deleteFile.operate();
mInvoker.putCommand(new UnDeleteFileCommand(deleteFile.saveState()));
}
public void undo() {
mInvoker.run();
}
}
//模拟一个测试场景
public static void testUndo() {
String newFile = "/abc/120.txt";
String file1 = "/abc/121.txt";
String file2 = "/abc/122.txt";
String file3 = "/abc/123.txt";
String file4 = "/abc/124.txt";
FileOperation operation = new FileOperation();
operation.create(newFile);
operation.copy(file1, file2);
operation.move(file3, file4);
operation.delete(file2);
operation.undo();
operation.undo();
operation.undo();
operation.undo();
}
public static void main(String[] argv) {
testUndo();
}
}