JAVA设计模式之备忘录模式

一 概述

备忘录模式听起来特别高深,其实可能写过几年代码的都不知不觉的用了很多次了。模式的名称其实已经很形象的反映出其作用了:就是为了在某一时刻把当前的状态记录下来,以后再恢复到那时的状态。

1.1 定义

在不破坏封闭的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,从而可以将对象恢复到原先保存的状态。

  • 备忘录模式属于行为型模式
  • 备忘录模式比较适合用于功能复杂,但是需要维护和纪录历史的地方,或者是需要保存一个或者多个属性的地方;在未来某个时刻需要时,将其还原到原来纪录的状态

1.2 使用场景

当你正在开发一个功能,这个功能需要存档的时候就应该想到它。例如游戏,文档编辑器等等,都需要在你下次重新打开的时候恢复到你关闭它时候的状态。

1.3 UML 类图

在这里插入图片描述
角色说明:

  • Originator(发起人角色):我们知道备忘录模式就是要完成保存状态,然后恢复状态的功能。那么保存和恢复谁的状态呢?对了,就是这个角色的状态
  • Memoto(备忘录角色):这个就比较简单了,就是一个存储状态的类,里面没有业务逻辑,一般是一个 POJO
  • Caretaker(负责人角色):负责保存备忘录(Memoto),不能对备忘录(Memoto)的内容进行操作和访问,只能将备忘录传递给其他对象

二 实现

王二狗在享元模式那集帮他儿子开发了个五子棋游戏,儿子玩过几次后提出了一个痛点:

儿子:老爸,我经常玩到一半的时候妈妈就喊我去睡觉,然后我睡醒了打开它就变成新的了,你能不能在我睡觉的时候把它存起来,那样我玩的时候就能从上次停下来的地方开始了。。。

二狗:听着那小子一直巴巴,二狗陷入了沉思:养个儿子真费劲,这么小就使唤老子,长大了还的帮着娶媳妇,关键是房子 tm 那么贵,以后还得带孙子。。。这要是个闺女多好。。。

儿子:老爸,你干啥呢,你听见我说话了吗。。。

二狗:听见拉,听见啦,我这就给你弄,但是你以后拉粑粑让你妈擦屁股啊,不要叫我了,听见没?

二狗思索了一下,此情此景正是备忘录模式的用武之地。

2.1 定义 Originator

这个类是游戏类,我们就是要保存和恢复游戏进度。它内部提供了两个方法,一个对外提供备忘录,里面封装了其要恢复的内部状态。另一个是从外部接收备忘录,用来恢复内部状态。

可能有的同学要问了:为什么要这么搞呢?直接写个 setter 不香吗?其实核心思想还是为了封装。这里的 currentScore 是 GameOriginator 的内部状态,我们不愿意对外暴露,所以使用另一个对象包起来。这样的内部状态一般会有很多,这里做了简化。

public class GameOriginator {
    private int currentScore;
    
    //将需要保存的状态分装在Memento里对外提供
    public GameProgressMemento saveProcess() {
        return new GameProgressMemento(currentScore);
    }

    //通过从外部接收的Memento恢复状态
    public void restoreProcess(GameProgressMemento memento) {
        currentScore = memento.getScore();
    }

    //对内部状态的使用
    public void playGame() {
        System.out.println("------------------开始游戏------------------");
        System.out.println("当前分数为:"+ currentScore);
        System.out.println("杀死一个小怪物得1分");
        currentScore++;
        System.out.println(String.format("总分为:%d", currentScore));
    }

    public void exitGame(){
        System.out.println("退出游戏");
        currentScore=0;
        System.out.println("-----------------退出游戏-------------------");
    }
}

2.2 构建备忘录 Memento

这个类最简单,其基本上就是一个 POJO。它不包含业务逻辑,只包含状态数据,结构由要保存的状态类决定。例如我们这里只保存一个内部状态,游戏分数。

public class GameProgressMemento {
    private int score;

    public GameProgressMemento(int score) {
        this.score = score;
    }

    public int getScore() {
        return score;
    }
}

2.3 构建 CareTaker

如果说备忘录模式有一点点技巧的话,也就是这个类了。CareTaker 相对于 Originator 来说是一个外部组件,它帮助 Originator 保存了状态,相当于 Originator 将自己某一个时刻的状态保存到了外部。

当我们要保存状态时,使用此类的 saveMemento。当我们要恢复状态时,使用此类的 getMemento。

public class GameCareTaker {

    private List<GameProgressMemento> memento= new ArrayList<>();

    public void saveMemento(GameProgressMemento memento) {
        this.memento.add(memento);
    }

    public GameProgressMemento getMemento(int index) {
        return this.memento.get(index);
    }
}

2.4 客户端测试

public class MementoClient {
    public void replayGame() {
        GameOriginator originator = new GameOriginator();
        GameCareTaker careTaker = new GameCareTaker();
        //玩游戏
        originator.playGame();
        //保存进度
        careTaker.saveMemento(originator.saveProcess());
        //退出游戏
        originator.exitGame();

        //重新打开游戏,恢复进度
        originator.restoreProcess(careTaker.getMemento(0));
        originator.playGame();
    }
}

输出结果:

------------------开始游戏------------------
当前分数为:0
杀死一个小怪物得1分
总分为:1
退出游戏
-----------------退出游戏-------------------
------------------开始游戏------------------
当前分数为:1
杀死一个小怪物得1分
总分为:2

三 总结

3.1 特点

  • 首先识别出 Originator 需要保存的状态,然后构建一个备忘录类 Memento
  • Originator 需要提供两个方法,一个用于对外提供包含内部状态的备忘录,一个用于使用外部传递进来的备忘录恢复内部状态
  • CareTaker 负责保存备忘录,并提供访问方法

3.2 优点

  • 能够让状态回滚到某一时刻的状态
  • 实现了状态保存对象的封装,用户无需关心其实现细节

3.3 缺点

要保存的对象如果成员变量过多的话,资源消耗也会相应增多。

四 Android 中的源码实例分析

Android 中的 Activity 就提供了状态保存机制来保证 Activity 在被系统回收后能够恢复当前 Activity 的数据。

这一机制实际上就是 onSaveInstanceState 和 onRestoreInstanceState。

  • onSaveInstanceState 就是用来保存当前 Activity 的状态
  • onRestoreInstanceState 则是用来恢复 Activity 的状态

4.1 onSaveInstanceState 源码

    protected void onSaveInstanceState(Bundle outState) {// 保存各种状态

        //1.保存 Activity 对应 Window 的状态信息
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

        //2.如果存在 Fragments,则保存所有 Fragments 的状态信息
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }

        //3.如果设置了 ActivityLifecycleCallbacks 回调,那么会调用
        // ActivityLifecycleCallbacks的onSaveInstanceState 来进行保存状态信息
        getApplication().dispatchActivitySaveInstanceState(this, outState);
    }

4.2 .2 onRestoreInstanceState 源码

   protected void onRestoreInstanceState(Bundle savedInstanceState) {
        if (mWindow != null) {
            // 获取保存过的 window 状态信息
            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
            // 如果存在状态信息,则 window 进行恢复
            if (windowState != null) {
                mWindow.restoreHierarchyState(windowState);
            }
        }
    }

4.3 总结

  • Activity 的状态保存与恢复其实是个比较复杂的东西,很多细节这里都没说到,有兴趣的同学可以找相关资料研究下,这里主要还是讲设计模式在 Android 源码中的应用
  • 在上面的源码中,Activity 实际上就是负责人角色(Caretaker),负责保存和恢复 UI 信息;Activity、View、ViewGroup、Fragment 等都是发起人角色(Originator),他们各自负责需要保存的信息;而备忘录角色(Memoto)则是 Bundle 类了,相关状态信息都是保存在 Bundle 中
  • 此外,Canvas 中的 Save()和 Restore()也是使用到了备忘录模式,有兴趣的可以去看下
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值