Android中的设计模式-备忘录模式

公元1722年,康熙皇帝驾崩于北京畅春园,步军统领隆科多取出了藏在正大光明匾额后面的遗诏,宣布四阿哥胤禛继承皇位。康熙为什么采用遗诏,而不是自己宣布继承人?还不是为了防止出现意外,来不及宣布人就没了;别人能够知道和篡改遗诏的内容吗?别人敢吗?!这样,康熙使用遗诏的方式,在龙驭宾天后,恢复了“皇帝类”的另一个对象实例:雍正。康熙爷在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();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值