0
点赞
收藏
分享

微信扫一扫

浅学设计模式之备忘录模式(15/23)


1. 备忘录模式的概念

备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

备忘录模式就像游戏中的存档,在某个时候我们想要回退到之前状态是,使用存档就可以了。备忘录提供了这么一套代码的思想。

它是一种行为模式。用于保存当前的状态,并且在之后可以再次恢复到此状态。
这种看似在程序中很少出现的用法,我们是否有机会使用到呢?

2. UML图和模板代码

我们来看下备忘录模式的UML图:

浅学设计模式之备忘录模式(15/23)_数据


上面一共有三个对象,他们的作用和关联是:

  • ​Originator​​​(发起人)
    负责创建一个备忘录Memento,可以记录、恢复自身的内部状态。同时Originator还可以根据需要决定Memento存储自身的哪些内部状态。
  • ​Memento​​​(备忘录)
    用于存储Originator的状态,并且可以防止Originator以外的对象访问Memento
  • ​Caretaker​​ 负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。

下面是模板代码,我们首先需要保存的数据源类:

public class Originator {
// 需要保存的属性, 可以有多个
private String state;

// 创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
public Memento createMemento(){
return new Memento(state);
}

public void setMemento(Memento memento){
state = memento.getState();
}

public String getState() {
return state;
}

// 恢复状态
public void setState(String state) {
this.state = state;
}

@Override
public String toString() {
return "Originator{" +
"state='" + state + '\'' +
'}';
}
}

它就是我们平时使用的类,它可以做很多事,在这个模式中,它多了一套代码,就是用来创建和设置Memento对象。

我们来创建一个备忘录类,来存放发起类想要存放的状态:

public class Memento {
private String state;

// 将需要保存的数据导入
public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}
}

最后是 Caretaker,它和备忘录类是聚合关系,它里面可以维护一份备忘录类列表,用于做整体的操作,相当于我们常用Manager类:

public class Caretaker {
private Memento memento;

public Memento getMemento() {
return memento;
}

public void setMemento(Memento memento) {
this.memento = memento;
}
}

最后看看客户端中的使用:

public static void main(String[] args) {
Originator o = new Originator();
o.setState("On"); // Originator初始状态,状态为“On”
System.out.println(o);

Caretaker c = new Caretaker();
c.setMemento(o.createMemento()); // 保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节

o.setState("Off"); // 将Originator的状态改为 “Off”
System.out.println(o);

o.setMemento(c.getMemento());
System.out.println(o); // 恢复初始状态
}

可以看到,我们把保存状态就写成了一句话:​​c.setMemento(o.createMemento());​​ 这样做的好处就是:把要保存的细节给封装在了Memento中了,哪一天要更改保存的细节也不用影响客户端了。

所以我们可以知道备忘录模式的使用场景:

  • Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性的一小部分时,Originator可以根据保存的Memto信息还原到前一状态。

很明显,就是用于撤销/恢复 的场景,当然了,撤销/恢复的实现我们可能早就会实现了,就是使用两个栈(一个撤销栈、一个恢复栈)用来保存状态。
而在备忘录模式中,相当于把两个栈放在了 ​​​Caretaker​​中。

3. Android中备忘录模式的应用

Android中也有使用到备忘录模式的地方,而且我们都很熟知,那就是 ​​Activity.onSaveInstanceState​​​ 和 ​​Activity.onRestoreInstanceState​​。

当Activity不是正常方式退出,且Activity在随后的时间内被系统杀死之前会调用,并且在下次返回Activity时恢复这些数据。

通过这两个函数,开发人员能够在某些特殊场景下存储于界面相关的信息,提升用户体验。
例如,用户在写了一大段信息之后,此时一个电话打了进来,用户的短信输入界面退到后台,如果在打电话的过程中短信应用被系统杀死了,那么在用户再次进入信息界面时,上一次输入的内容将会不复存在。
Activity的状态存储机制正是为了应对这情况出现的。那么它们的原理是怎样的呢?我们还是来看看源码:

protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); // 1

outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p); // 2
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState); // 3
}

注释1: 存储窗口的视图树的状态
注释2: 存储Fragment的状态
注释3: 调用Activity的 ​​​ActivityLifecycleCallbacks.onSaveInstanceState()​​,如果说用户在Application类设置了lifecycle的话。

这里就不细细深入源码了,我们只用知道,在执行了第一步后,会从根View开始递归到子View将带有id的视图存放起来。

我们知道AMS层管理者Activity的生命周期和信息的存储,每个Activity在AMS会存放一个 ​​ActivityClientRecord​​​,AMS会有一个列表维护他们。而 ​​onSaveInstanceState()​​​会在页面销毁前,准确的说是 ​​onStop()​​​前,将视图状态等信息存放到该ActivityClientRecord的​​state​​成员变量中。

在Activity的启动流程中,如果AMS发现该Activity的 ​​ActivityClientRecord.state​​​不为空,则说明Activity是调用过onSaveInstanceState方法来存储信息的,那么在调用了 Activity的​​onCreate()​​​后,就会调用 ​​onRestoreInstanceState()​​来讲数据渲染在Activity上。

这就是Android体现备忘录模式的地方,来看看这些类的关系

  • originator发起者
    就是Activity、Fragment、ViewGroup这些需要保存状态的类
  • momento备忘录
    就是存放状态的地方,在上面的例子中,备忘录显然是 Bundle类。它通过序列化存储了数据
  • Caretaker
    显然管理备忘录的就是​​​ActivityManagerService​​​ 这个大总管。无论是​​onSaveInstanceState()​​​ 还是​​onRestoreInstanceState()​​都是他调用的。

最后,补一下 ​​onSaveInstanceState()​​的调用时机,因为这个方法不是一定会调用的,它会出现在下面这些场景中:

  • 按Home键
    因为系统不知道在按下Home键后要去运行多少个其他程序,所以这里会调用这个方法来提供开发人员存储信息
  • 长按Home
  • 按下电源键(关闭屏幕)
  • 从一个Activity中启动一个新的Activity时
  • 屏幕方向切换,从竖屏切换到横屏时
  • 电话打入等情况时

总而言之就是,不是用户主动退出某个Activity或者跳转到其他Activity的情况下就会触发 ​​onSaveInstanceState()​​。

4. 总结

来总结下备忘录模式的使用场景:

  1. 需要保存一个对象在某一个时刻的状态或部分状态。
  2. 如果用一个接口来让其他对象得到这个状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。

在Android中, ​​onSaveInstanceState()​​​和 ​​onRestoreInstanceState()​​体现了备忘录模式,用于恢复Activity在不正常死亡下的状态恢复。

该模式的优点有:

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便的回到某个历史状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点有:

  • 因为要存储状态,所以必须要进行数据的拷贝,如果要存储的状态比较多,那么因为拷贝就会消耗很多资源,这势必会产生内存的耗费。


举报

相关推荐

0 条评论