備忘錄(Memento)模式

  備忘錄模式又叫作快照模式或者Token模式。java

  備忘錄對象是一個用來存儲另外一個對象內部狀態的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捕捉住,並外部化,存儲起來,從而能夠在未來合適的時候把這個對象還原到存儲起來的狀態。備忘錄模式經常與命令模式和迭代子模式一塊兒使用。數據庫

  常見的系統每每不止存儲一個狀態,而是須要存儲多個狀態。這些狀態經常是一個對象歷史發展的不一樣階段的快照,存儲這些快照的備忘錄對象叫作此對象的歷史;某一個快照所處的位置叫作檢查點。ide

1.角色

 1.備忘錄角色

備忘錄角色有以下責任:函數

  (1)將發起人(Originator)對象的內戰狀態存儲起來。備忘錄能夠根據發起人對象的判斷來決定存儲多少發起人(Originator)對象的內部狀態。this

  (2)備忘錄能夠保護其內容不被髮起人(Originator)對象以外的任何對象所讀取。spa

  備忘錄有兩個等效的接口:設計

  ●  窄接口:負責人(Caretaker)對象(和其餘除發起人對象以外的任何對象)看到的是備忘錄的窄接口(narrow interface),這個窄接口只容許它把備忘錄對象傳給其餘的對象。rest

  ●  寬接口:與負責人對象看到的窄接口相反的是,發起人對象能夠看到一個寬接口(wide interface),這個寬接口容許它讀取全部的數據,以便根據這些數據恢復這個發起人對象的內部狀態。code

2.  發起人角色

發起人角色有以下責任:對象

  (1)建立一個含有當前的內部狀態的備忘錄對象。

  (2)使用備忘錄對象存儲其內部狀態。

3.負責人(Caretaker)角色

負責人角色有以下責任:

  (1)負責保存備忘錄對象。

  (2)不檢查備忘錄對象的內容。

 

2.  模式的實現

1. 白箱備忘錄模式的實現

  備忘錄角色對任何對象都提供一個接口,即寬接口,備忘錄角色的內部所存儲的狀態就對全部對象公開。所以這個實現又叫作「白箱實現」。

結構:

 

 代碼以下:

發起人角色代碼:利用新建的備忘錄對象將本身的內部狀態存儲起來

/**
 * 發起人角色
 *
 */
public class Originator {

    private String state;

    /**
     * 工廠方法,返回一個新的備忘錄對象
     */
    public Memento createMemento() {
        return new Memento(state);
    }

    /**
     * 將發起人恢復到備忘錄對象所記載的狀態
     */
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        System.out.println("當前狀態:" + this.state);
    }
}

 

備忘錄角色類,備忘錄對象將發起人對象傳入的狀態存儲起來。

/**
 * 備忘錄角色
 *
 */
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 retrieveMemento() {
        return this.memento;
    }

    /**
     * 備忘錄的賦值方法
     */
    public void saveMemento(Memento memento) {
        this.memento = memento;
    }
}

 

客戶端:

public class Client {

    public static void main(String[] args) {
        // 發起人,改變負責人對象的狀態
        Originator o = new Originator();
        o.setState("On");

        // 利用發起人建立備忘錄
        Memento memento = o.createMemento();

        // 負責人,並將發起人對象的備忘錄儲存起來
        Caretaker c = new Caretaker();
        c.saveMemento(memento);

        // 修改發起人的狀態
        o.setState("Off");

        // 獲取負責人保存的備忘錄狀態
        Memento retrieveMemento = c.retrieveMemento();

        // 恢復發起人對象的狀態
        o.restoreMemento(retrieveMemento);
        System.out.println(o.getState());
    }
}

結果:

當前狀態:On
當前狀態:Off
On

    在上面客戶端角色裏面,首先將發起人對象的狀態設置成「On」,並建立一個備忘錄對象將這個狀態存儲起來;而後將發起人對象的狀態改爲「Off」;最後又將發起人對象恢復到備忘錄對象所存儲起來的狀態,即「On」狀態。

 

時序圖以下:

將發起人對象的狀態存儲到白箱備忘錄對象中去的時序圖:

能夠看出系統運行的時序是這樣的:
(1)將發起人對象的狀態設置成「On」。
(2)調用發起人角色的createMemento()方法,建立一個備忘錄對象將這個狀態存儲起來。
(3)將備忘錄對象存儲到負責人對象中去。

 

將發起人對象恢復到備忘錄對象所記錄的狀態的時序圖以下所示:

能夠看出,將發起人對象恢復到備忘錄對象所記錄的狀態時,系統的運行時序是這樣的:
(1)將發起人狀態設置成「Off」。
(2)將備忘錄對象從負責人對象中取出。
(3)將發起人對象恢復到備忘錄對象所存儲起來的狀態,即「On」狀態。

 

白箱實現的優缺點:

  優勢是實現比較簡單,經常用做教學目的。缺點是破壞對發起人狀態的封裝。(並且上面的例子只能存儲一個狀態,又叫作一個檢查點)

 

2.黑箱實現

  備忘錄角色對發起人(Originator)角色對象提供一個寬接口,而爲其餘對象提供一個窄接口。這樣的實現叫作「黑箱實現」。

  在JAVA語言中,實現雙重接口的辦法就是將備忘錄角色類設計成發起人角色類的內部成員類。
  將Memento設成Originator類的內部類,從而將Memento對象封裝在Originator裏面;在外部提供一個標識接口MementoIF給Caretaker以及其餘對象。這樣,Originator類看到的是Menmento的全部接口,而Caretaker以及其餘對象看到的僅僅是標識接口MementoIF所暴露出來的接口。

類圖以下:

 

源碼:

發起人角色:定義了一個內部的Memento類。因爲此Memento類的所有接口都是私有的,所以只有它本身和發起人類能夠調用。

/**
 * 發起人角色
 */
public class Originator {

    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        System.out.println("賦值狀態:" + state);
    }

    /**
     * 工廠方法,返還一個新的備忘錄對象
     */
    public MementoIF createMemento() {
        return new Memento(state);
    }

    /**
     * 發起人恢復到備忘錄對象記錄的狀態
     */
    public void restoreMemento(MementoIF memento) {
        this.setState(((Memento) memento).getState());
    }

    /**
     * 私有內部備忘錄角色實現寬接口
     * 
     * @author Administrator
     *
     */
    private class Memento implements MementoIF {

        private String state;

        /**
         * 構造方法
         */
        private Memento(String state) {
            this.state = state;
        }

        private String getState() {
            return state;
        }

        private void setState(String state) {
            this.state = state;
        }
    }
}

 

窄接口MementoIF,這是一個標識接口,所以它沒有定義出任何的方法。

/**
 * 標識接口
 *
 */
public interface MementoIF {

}

 

負責人角色類Caretaker可以獲得的備忘錄對象是以MementoIF爲接口的,因爲這個接口僅僅是一個標識接口,所以負責人角色不可能改變這個備忘錄對象的內容。

/**
 * 負責人角色
 * 
 * @author Administrator
 *
 */
public class Caretaker {

    private MementoIF memento;

    /**
     * 備忘錄取值方法
     */
    public MementoIF retrieveMemento() {
        return memento;
    }

    /**
     * 備忘錄賦值方法
     */
    public void saveMemento(MementoIF memento) {
        this.memento = memento;
    }
}

 

客戶端:

public class Client {

    public static void main(String[] args) {
        // 建立發起人
        Originator o = new Originator();

        // 建立負責人
        Caretaker c = new Caretaker();

        // 改變負責人對象的狀態
        o.setState("On");

        // 建立備忘錄對象,並將發起人對象的狀態存儲起來
        c.saveMemento(o.createMemento());

        // 修改發起人對象的狀態
        o.setState("Off");

        // 恢復發起人對象的狀態
        o.restoreMemento(c.retrieveMemento());
    }
}

結果:

賦值狀態:On
賦值狀態:Off
賦值狀態:On

 

時序以下:

(1)將發起人對象的狀態設置爲「On」。
(2)調用createMemento()方法,建立一個備忘錄對象將這個狀態存儲起來(此時createMemento()方法還回的明顯類型是MementoIF接口,真實類型爲Originator內部的Memento對象)。
(3)將備忘錄對象存儲到負責人對象中去。因爲負責人對象拿到的僅是MementoIF接口,所以沒法讀出備忘錄對象內部的狀態。
(4)將發起人對象的狀態設置爲「Off」。
(5)調用負責人對象的retrieveMemento()方法將備忘錄對象取出。注意此時僅能獲得MementoIF接口,所以沒法讀出此對象的內部狀態。
(6)調用發起人對象的restoreMemento()方法將發起人對象的狀態恢復成備忘錄對象所存儲的起來的狀態,即「On」狀態。因爲發起人對象的內部類Memento實現了MementoIF接口,這個內部類是傳入的備忘錄對象的真實類型,所以發起人對象能夠利用內部類Memento的私有接口讀出此對象的內部狀態。

 

3.多重檢查點

  前面的例子都是隻存儲一個狀態的簡單實現,也能夠叫作只有一個檢查點。常見的系統每每須要存儲不止一個狀態,而是須要存儲多個狀態,或者叫作有多個檢查點。

  備忘錄模式能夠將發起人對象的狀態存儲到備忘錄對象裏面,備忘錄模式能夠將發起人對象恢復到備忘錄對象所存儲的某一個檢查點上。下面給出一個示意性的、有多重檢查點的備忘錄模式的實現。

類圖以下:

 

源代碼:

發起人角色:將狀態存在集合中,每個狀態都有一個指數index,叫作檢查點指數。

import java.util.ArrayList;
import java.util.List;

/**
 * 發起人角色
 * 
 * @author Administrator
 *
 */
public class Originator {

    private List<String> states;
    // 檢查點指數
    private int index;

    /**
     * 構造函數
     */
    public Originator() {
        states = new ArrayList<String>();
        index = 0;
    }

    /**
     * 工廠方法,返還一個新的備忘錄對象
     */
    public Memento createMemento() {
        return new Memento(states, index);
    }

    /**
     * 將發起人恢復到備忘錄對象記錄的狀態上
     */
    public void restoreMemento(Memento memento) {
        states = memento.getStates();
        index = memento.getIndex();
    }

    /**
     * 狀態的賦值方法
     */
    public void setState(String state) {
        states.add(state);
        index++;
    }

    /**
     * 輔助方法,打印全部狀態
     */
    public void printStates() {
        for (String state : states) {
            System.out.println(state);
        }
    }
}

 

備忘錄角色:存儲任意多的狀態,外界能夠用檢查點指數index來取出檢查點上的狀態(克隆了傳入的states,不可使用同一個引用)

import java.util.ArrayList;
import java.util.List;

/**
 * 備忘錄角色
 * 
 * @author Administrator
 *
 */
public class Memento {

    private List<String> states;
    private int index;

    /**
     * 構造函數
     */
    public Memento(List<String> states, int index) {
        this.states = new ArrayList<String>(states);
        this.index = index;
    }

    public List<String> getStates() {
        return states;
    }

    public int getIndex() {
        return index;
    }

}

 

負責人角色:根據檢查點指數index來恢復發起人角色的狀態,也能夠根據檢查點指數index來取消一個檢查點。

import java.util.ArrayList;
import java.util.List;

/**
 * 負責人角色
 * 
 * @author Administrator
 *
 */
public class Caretaker {

    private Originator o;
    private List<Memento> mementos = new ArrayList<Memento>();
    private int current;

    /**
     * 構造函數
     */
    public Caretaker(Originator o) {
        this.o = o;
        current = 0;
    }

    /**
     * 建立一個新的檢查點
     */
    public int createMemento() {
        Memento memento = o.createMemento();
        mementos.add(memento);
        return current++;
    }

    /**
     * 將發起人恢復到某個檢查點
     */
    public void restoreMemento(int index) {
        Memento memento = mementos.get(index);
        o.restoreMemento(memento);
    }

    /**
     * 將某個檢查點刪除
     */
    public void removeMemento(int index) {
        mementos.remove(index);
    }
}

 

客戶端:

public class Client {
    public static void main(String[] args) {
        Originator o = new Originator();
        Caretaker c = new Caretaker(o);

        // 改變狀態
        o.setState("state 0");
        // 創建一個檢查點
        c.createMemento();

        // 改變狀態
        o.setState("state 1");
        // 創建一個檢查點
        c.createMemento();

        // 改變狀態
        o.setState("state 2");
        // 創建一個檢查點
        c.createMemento();

        // 改變狀態
        o.setState("state 3");
        // 創建一個檢查點
        c.createMemento();

        // 打印出全部檢查點
        o.printStates();

        System.out.println("-----------------恢復檢查點2-----------------");
        // 恢復到第二個檢查點
        c.restoreMemento(1);
        // 打印出全部檢查點
        o.printStates();
    }
}

結果:

state 0
state 1
state 2
state 3
-----------------恢復檢查點2-----------------
state 0
state 1
state 2

 

  能夠看出,客戶端角色經過不斷改變發起人角色的狀態,並將之存儲在備忘錄裏面。經過指明檢查點指數能夠將發起人角色恢復到相應的檢查點所對應的狀態上。

 

4.自述歷史模式

  所謂「自述歷史」模式(History-On-Self Pattern)實際上就是備忘錄模式的一個變種。在備忘錄模式中,發起人(Originator)角色、負責人(Caretaker)角色和備忘錄(Memento)角色都是獨立的角色。雖然在實現上備忘錄類能夠成爲發起人類的內部成員類,可是備忘錄類仍然保持做爲一個角色的獨立意義。在「自述歷史」模式裏面,發起人角色本身兼任負責人角色。

  備忘錄角色有以下責任:
  (1)將發起人(Originator)對象的內部狀態存儲起來。
  (2)備忘錄能夠保護其內容不被髮起人(Originator)對象以外的任何對象所讀取。
  發起人角色有以下責任:
  (1)建立一個含有它當前的內部狀態的備忘錄對象。
  (2)使用備忘錄對象存儲其內部狀態。
  客戶端角色有負責保存備忘錄對象的責任。

 

源代碼:

/**
 * 標識接口(做爲備忘錄對象的身份和標識)
 * 
 * @author Administrator
 *
 */
public interface MementoIF {

}

 

發起人角色:

/**
 * 發起人角色(兼職發起人角色)
 * 
 * @author Administrator
 *
 */
public class Originator {

    public String state;

    /**
     * 改變狀態
     */
    public void changeState(String state) {
        this.state = state;
        System.out.println("狀態改變爲:" + state);
    }

    /**
     * 工廠方法,返還一個新的備忘錄對象
     */
    public Memento createMemento() {
        return new Memento(this);
    }

    /**
     * 將發起人恢復到備忘錄對象所記錄的狀態上
     */
    public void restoreMemento(MementoIF memento) {
        Memento m = (Memento) memento;
        changeState(m.state);
    }

    private class Memento implements MementoIF {

        private String state;

        /**
         * 構造方法
         */
        private Memento(Originator o) {
            this.state = o.state;
        }

        private String getState() {
            return state;
        }

    }
}

 

客戶端:

public class Client {

    public static void main(String[] args) {
        Originator o = new Originator();
        // 修改狀態
        o.changeState("state 0");
        // 建立備忘錄
        MementoIF memento = o.createMemento();

        // 修改狀態
        o.changeState("state 1");

        // 按照備忘錄恢復對象的狀態
        o.restoreMemento(memento);
    }

}

結果:

狀態改變爲:state 0
狀態改變爲:state 1
狀態改變爲:state 0

 

  此種模式簡單易懂,多是備忘錄模式最爲流行的實現形式。

 

總結:

意圖:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。

主要解決:所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態,這樣能夠在之後將對象恢復到原先保存的狀態。

什麼時候使用:不少時候咱們老是須要記錄一個對象的內部狀態,這樣作的目的就是爲了容許用戶取消不肯定或者錯誤的操做,可以恢復到他原先的狀態,使得他有"後悔藥"可吃。

如何解決:經過一個備忘錄類專門存儲對象狀態。

關鍵代碼:客戶不與備忘錄類耦合,與備忘錄管理類耦合。

應用實例: 一、後悔藥。 二、打遊戲時的存檔。 三、Windows 裏的 ctri + z。 四、IE 中的後退。 四、數據庫的事務管理。

優勢: 一、給用戶提供了一種能夠恢復狀態的機制,可使用戶可以比較方便地回到某個歷史的狀態。 二、實現了信息的封裝,使得用戶不須要關心狀態的保存細節。

缺點:消耗資源。若是類的成員變量過多,勢必會佔用比較大的資源,並且每一次保存都會消耗必定的內存。

使用場景: 一、須要保存/恢復數據的相關狀態場景。 二、提供一個可回滾的操做。

注意事項: 一、爲了符合迪米特原則,還要增長一個管理備忘錄的類。 二、爲了節約內存,可以使用原型模式+備忘錄模式。

相關文章
相關標籤/搜索