设计模式备忘录模式

1.备忘录模式的定义及使用场景
备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存的这些对象状态的完整性以及内部实现不向外暴露。
定义:
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象回复到原先保存的状态
使用场景:

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

Paste_Image.png

2. 备忘录模式的优缺点
2.1优点
给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
实现了信息的封装,使得用户不需要关心状态的保持细节
2.2缺点
消耗资源,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存

3.注意事项

备忘录的生命期备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理
备忘录的性能不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中),主要原因是一是控制不了备忘录建立的对象数量;而是大对象的建立是要消耗资源的,系统的性能需要考虑。
4. 备忘录模式的实现方式

public class Memento {
    //发起人的内部状态
    private String state="";
    public Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
}```

public class Caretaker {
//备忘录对象
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}```

public class Originator {
    //内部状态
    private String state = "";
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    //创建一个备忘录
    public Memento createMemento() {
        return new Memento(state);
    }
    //恢复一个备忘录
    public void restoreMemento(Memento memento) {
        this.setState(memento.getState());
    }
}```

public class Test {
public static void main(String args[]) {
//定义出发起人
Originator originator = new Originator();
originator.setState("初始:1111111111111");
//定义出备忘录管理员
Caretaker caretaker = new Caretaker();
System.out.println(originator.getState());
//创建一个备忘录
caretaker.setMemento(originator.createMemento());
originator.setState("改变:22222222222222");
System.out.println(originator.getState());
originator.setState("恢复:restoreMemento");
originator.restoreMemento(caretaker.getMemento());
System.out.println(originator.getState());
}
}```
5. 备忘录模式在Android中的实际应用
在Android开发中,状态模式应用是Android中的状态保持,也就是里面的onSaveInstanceState和onRestoreInstanceState。当Activity不是正常方式退出,且Activity在随后的时间内被系统杀死之前会调用这两个方法让开发人员可以有机会存储Activity的相关信息,并且在下次放好Activity的时候恢复这些数据。
Activity:

protected void onSaveInstanceState(Bundle outState) {
 outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); //保存当前窗口的视图树的状态
 Parcelable p = mFragments.saveAllState(); //存储Fragment的状态
 if (p != null) {
 outState.putParcelable(FRAGMENTS_TAG, p);
 }
 getApplication().dispatchActivitySaveInstanceState(this, outState);
 }```
Window的实现为PhoneWindow:

/** {@inheritDoc} */
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
//通过SparseArray类来存储,这相当于一个key为整型的map
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
//mContentParent就是调用Activity的setContentView函数设置的内容视图,它是内容视图的根节点,在这里存储整棵树的结构
mContentParent.saveHierarchyState(states);
//将视图树结构放到outState中
outState.putSparseParcelableArray(VIEWS_TAG, states);
// 保存当前界面中获取了焦点的View
View focusedView = mContentParent.findFocus();
if (focusedView != null) {
if (focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
} else {
if (false) {
Log.d(TAG, "couldn't save which view has focus because the focused view "

  • focusedView + " has no id.");
    }
    }
    }
    // 存储整个面板的状态
    SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
    savePanelState(panelStates);
    if (panelStates.size() > 0) {
    outState.putSparseParcelableArray(PANELS_TAG, panelStates);
    }
    //存储actionBar的状态
    if (mDecorContentParent != null) {
    SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
    mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
    outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
    }
    return outState;
    }```
    在saveHierarchyState函数中,主要是存储了与当前UI、ActionBar相关的View状态。mContentParent就是我们通过Activity的setContentView函数设置的内容视图,他是这个内容视图的根节点。mContentParent是一个ViewGroup对象,但是saveHierachyState是在父类View中
public void saveHierarchyState(SparseArray<Parcelable> container) {
 dispatchSaveInstanceState(container);
 }```

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state); //将自身状态放到container中 key 为id value为自身状态
}
}
}```

//View类默认的存储的状态为空
 protected Parcelable onSaveInstanceState() {
  mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
  if (mStartActivityRequestWho != null) {
  BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
  state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
  return state;
  }
  return BaseSavedState.EMPTY_STATE;
  }```

恢复数据的调用过程如下,基本流程与保存类似
Activity:

protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}```
PhoneWindow:

/** {@inheritDoc} */
 @Override
 public void restoreHierarchyState(Bundle savedInstanceState) {
 if (mContentParent == null) {
 return;
 }
 SparseArray<Parcelable> savedStates
 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
 if (savedStates != null) {
 mContentParent.restoreHierarchyState(savedStates);
 }
 // restore the focused view
 int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
 if (focusedViewId != View.NO_ID) {
 View needsFocus = mContentParent.findViewById(focusedViewId);
 if (needsFocus != null) {
 needsFocus.requestFocus();
 } else {
 Log.w(TAG,
 "Previously focused view reported id " + focusedViewId
 + " during save, but can't be found during restore.");
 }
 }
 // restore the panels
 SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
 if (panelStates != null) {
 restorePanelState(panelStates);
 }
 if (mDecorContentParent != null) {
 SparseArray<Parcelable> actionBarStates =
 savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
 if (actionBarStates != null) {
 doPendingInvalidatePanelMenu();
 mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
 } else {
 Log.w(TAG, "Missing saved instance states for action bar views! " +
 "State will not be restored.");
 }
 }
 }```
View:

public void restoreHierarchyState(SparseArray<Parcelable> container) {
dispatchRestoreInstanceState(container);
}```

/**
* Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
* state for this view and its children. May be overridden to modify how restoring
* happens to a view's children; for example, some views may want to not store state
* for their children.
*
* @param container The SparseArray which holds previously saved state.
*
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID);
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}```

protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != null && !(state instanceof AbsSavedState)) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "

  • "received " + state.getClass().toString() + " instead. This usually happens "
  • "when two views of different type have the same id in the same hierarchy. "
  • "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
  • "other views do not use the same id.");
    }
    if (state != null && state instanceof BaseSavedState) {
    mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
    }
    }```
    在这个过程中,Activity扮演了Caretaker角色,负责存储、恢复UI的状态信息;Activity、Fragement、View、ViewGroup等对象为Originator角色,也就是需要存储状态的对象;Memento则是由Bundle类扮演。

Activity在停止之前会根据Activity的退出情景来选择是否需要存储状态,在重新启动该Activity时会判断ActivityClientRecord对象中是否存储了Activity的状态,如果含有状态则调用Activity的onRestoreInstanceState函数,从而使得Activity的UI效果与上次保持一致,这样一来,就保证了在非正常退出Activity时不会丢失数据的情况,很好地提升了用户体验。
出处:http://huangjunbin.com/page/2/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容