設(shè)計模式--備忘錄模式(Memento)

備忘錄模式(Memento)

每個人都有犯錯誤的時候,都希望有種“后悔藥”能彌補(bǔ)自己的過失,讓自己重新開始郑原,但現(xiàn)實是殘酷的唉韭。在計算機(jī)應(yīng)用中,客戶同樣會常常犯錯誤犯犁,能否提供“后悔藥”給他們呢属愤?當(dāng)然是可以的,而且是有必要的酸役。這個功能由“備忘錄模式”來實現(xiàn)住诸。

其實很多應(yīng)用軟件都提供了這項功能,如 Word涣澡、記事本贱呐、Photoshop、Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵時能撤銷當(dāng)前操作入桂,使文檔恢復(fù)到之前的狀態(tài)奄薇;還有在 IE 中的后退鍵蜡豹、數(shù)據(jù)庫事務(wù)管理中的回滾操作舟茶、玩游戲時的中間結(jié)果存檔功能、數(shù)據(jù)庫與操作系統(tǒng)的備份操作瘩欺、棋類游戲中的悔棋功能等都屬于這類蜘腌。

備忘錄模式能記錄一個對象的內(nèi)部狀態(tài)沫屡,當(dāng)用戶后悔時能撤銷當(dāng)前操作,使數(shù)據(jù)恢復(fù)到它原先的狀態(tài)逢捺。

模式的定義與特點

  • 備忘錄(Memento)模式的定義:
    在不破壞封裝性的前提下谁鳍,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)劫瞳,以便以后當(dāng)需要時能將該對象恢復(fù)到原先保存的狀態(tài)倘潜。該模式又叫快照模式。
  • 備忘錄模式(Memento)模式的優(yōu)點:
  1. 提供了一種可以恢復(fù)狀態(tài)的機(jī)制志于。當(dāng)用戶需要時能夠比較方便地將數(shù)據(jù)恢復(fù)到某個歷史的狀態(tài)涮因。
  2. 實現(xiàn)了內(nèi)部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外伺绽,其他對象都不能夠訪問這些狀態(tài)信息养泡。
  3. 簡化了發(fā)起人類。發(fā)起人不需要管理和保存其內(nèi)部狀態(tài)的各個備份奈应,所有狀態(tài)信息都保存在備忘錄中澜掩,并由管理者進(jìn)行管理,這符合單一職責(zé)原則杖挣。
  • 備忘錄模式(Memento)模式的缺點:
    資源消耗大肩榕。如果要保存的內(nèi)部狀態(tài)信息過多或者特別頻繁,將會占用比較大的內(nèi)存資源惩妇。

模式的結(jié)構(gòu)與實現(xiàn)

備忘錄模式的核心是設(shè)計備忘錄類以及用于管理備忘錄的管理者類株汉,現(xiàn)在我們來學(xué)習(xí)其結(jié)構(gòu)與實現(xiàn)筐乳。

1. 模式的結(jié)構(gòu)

備忘錄模式的主要角色如下。

  1. 發(fā)起人(Originator)角色: 記錄當(dāng)前時刻的內(nèi)部狀態(tài)信息乔妈,提供創(chuàng)建備忘錄和恢復(fù)備忘錄數(shù)據(jù)的功能蝙云,實現(xiàn)其他業(yè)務(wù)功能,它可以訪問備忘錄里的所有信息路召。
  2. 備忘錄(Memento)角色: 負(fù)責(zé)存儲發(fā)起人的內(nèi)部狀態(tài)勃刨,在需要的時候提供這些內(nèi)部狀態(tài)給發(fā)起人。
  3. 管理者(Caretaker)角色: 對備忘錄進(jìn)行管理优训,提供保存與獲取備忘錄的功能朵你,但其不能對備忘錄的內(nèi)容進(jìn)行訪問與修改。

備忘錄模式的結(jié)構(gòu)圖如圖 1 所示揣非。


圖1 備忘錄模式的結(jié)構(gòu)圖
2. 模式的實現(xiàn)

備忘錄模式的實現(xiàn)代碼如下:

package memento;
public class MementoPattern
{
    public static void main(String[] args)
    {
        Originator or=new Originator();
        Caretaker cr=new Caretaker();       
        or.setState("S0"); 
        System.out.println("初始狀態(tài):"+or.getState());           
        cr.setMemento(or.createMemento()); //保存狀態(tài)      
        or.setState("S1"); 
        System.out.println("新的狀態(tài):"+or.getState());        
        or.restoreMemento(cr.getMemento()); //恢復(fù)狀態(tài)
        System.out.println("恢復(fù)狀態(tài):"+or.getState());
    }
}
//備忘錄
class Memento
{ 
    private String state; 
    public Memento(String state)
    { 
        this.state=state; 
    }     
    public void setState(String state)
    { 
        this.state=state; 
    }
    public String getState()
    { 
        return state; 
    }
}
//發(fā)起人
class Originator
{ 
    private String state;     
    public void setState(String state)
    { 
        this.state=state; 
    }
    public String getState()
    { 
        return state; 
    }
    public Memento createMemento()
    { 
        return new Memento(state); 
    } 
    public void restoreMemento(Memento m)
    { 
        this.setState(m.getState()); 
    } 
}
//管理者
class Caretaker
{ 
    private Memento memento;       
    public void setMemento(Memento m)
    { 
        memento=m; 
    }
    public Memento getMemento()
    { 
        return memento; 
    }
}

程序運(yùn)行的結(jié)果如下:

初始狀態(tài):S0
新的狀態(tài):S1
恢復(fù)狀態(tài):S0

模式的實例

【例】利用備忘錄模式設(shè)計相親游戲抡医。

分析:假如有西施、王昭君早敬、貂蟬忌傻、楊玉環(huán)四大美女同你相親,你可以選擇其中一位作為你的愛人搞监;當(dāng)然水孩,如果你對前面的選擇不滿意,還可以重新選擇琐驴,但希望你不要太花心俘种;這個游戲提供后悔功能,用“備忘錄模式”設(shè)計比較合適.绝淡。

首先宙刘,先設(shè)計一個美女(Girl)類,它是備忘錄角色牢酵,提供了獲取和存儲美女信息的功能悬包;然后,設(shè)計一個相親者(You)類馍乙,它是發(fā)起人角色布近,它記錄當(dāng) 前時刻的內(nèi)部狀態(tài)信息(臨時妻子的姓名),并提供創(chuàng)建備忘錄和恢復(fù)備忘錄數(shù)據(jù)的功能丝格;最后撑瞧,定義一個美女棧(GirlStack)類,它是管理者角色显蝌,負(fù)責(zé)對備忘錄進(jìn)行管理季蚂,用于保存相親者(You)前面選過的美女信息,不過最多只能保存 4 個琅束,提供后悔功能扭屁。

客戶類設(shè)計成窗體程序,它包含美女棧(GirlStack)對象和相親者(You)對象涩禀,它實現(xiàn)了 ActionListener 接口的事件處理方法 actionPerformed(ActionEvent e)料滥,并將 4 大美女圖像和相親者(You)選擇的美女圖像在窗體中顯示出來。圖 2 所示是其結(jié)構(gòu)圖艾船。


圖2 相親游戲的結(jié)構(gòu)圖

程序代碼如下:

package memento;
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
public class DatingGame
{
    public static void main(String[] args)
    {
        new DatingGameWin();
    }
}
//客戶窗體類
class DatingGameWin extends JFrame implements ActionListener
{
    private static final long serialVersionUID=1L;   
    JPanel CenterJP,EastJP;
    JRadioButton girl1,girl2,girl3,girl4;
    JButton button1,button2;
    String FileName;
    JLabel g;
    You you;
    GirlStack girls;
    DatingGameWin()
    {
        super("利用備忘錄模式設(shè)計相親游戲");
        you=new You();
        girls=new GirlStack();       
        this.setBounds(0,0,900,380);            
        this.setResizable(false);
        FileName="src/memento/Photo/四大美女.jpg";   
        g=new JLabel(new ImageIcon(FileName),JLabel.CENTER);
        CenterJP=new JPanel();
        CenterJP.setLayout(new GridLayout(1,4));
        CenterJP.setBorder(BorderFactory.createTitledBorder("四大美女如下:"));
        CenterJP.add(g);   
        this.add("Center",CenterJP);
        EastJP=new JPanel();
        EastJP.setLayout(new GridLayout(1,1));
        EastJP.setBorder(BorderFactory.createTitledBorder("您選擇的愛人是:"));
        this.add("East",EastJP);
        JPanel SouthJP=new JPanel();      
        JLabel info=new JLabel("四大美女有“沉魚落雁之容葵腹、閉月羞花之貌”,您選擇誰屿岂?");
        girl1=new JRadioButton("西施",true);
        girl2=new JRadioButton("貂蟬");
        girl3=new JRadioButton("王昭君");       
        girl4=new JRadioButton("楊玉環(huán)");
        button1=new JButton("確定");
        button2=new JButton("返回");
        ButtonGroup group=new ButtonGroup();
        group.add(girl1);
        group.add(girl2);
        group.add(girl3);
        group.add(girl4);
        SouthJP.add(info);
        SouthJP.add(girl1);
        SouthJP.add(girl2);
        SouthJP.add(girl3);
        SouthJP.add(girl4);
        SouthJP.add(button1);
        SouthJP.add(button2);
        button1.addActionListener(this);
        button2.addActionListener(this);
        this.add("South",SouthJP);        
        showPicture("空白");
        you.setWife("空白");
        girls.push(you.createMemento());    //保存狀態(tài)
    }
    //顯示圖片
    void showPicture(String name)
    {
        EastJP.removeAll(); //清除面板內(nèi)容
        EastJP.repaint(); //刷新屏幕
        you.setWife(name);        
        FileName="src/memento/Photo/"+name+".jpg";               
        g=new JLabel(new ImageIcon(FileName),JLabel.CENTER);                   
        EastJP.add(g);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);            
    }
    @Override
    public void actionPerformed(ActionEvent e)
    {
        boolean ok=false;
        if(e.getSource()==button1)
        {
            ok=girls.push(you.createMemento());    //保存狀態(tài)  
            if(ok && girl1.isSelected())
            {
                showPicture("西施");
            }
            else if(ok && girl2.isSelected())
            {
                showPicture("貂蟬");
            }
            else if(ok && girl3.isSelected())
            {
                showPicture("王昭君");
            }
            else if(ok && girl4.isSelected())
            {
                showPicture("楊玉環(huán)");
            }               
        }
        else if(e.getSource()==button2)
        {
            you.restoreMemento(girls.pop()); //恢復(fù)狀態(tài)
            showPicture(you.getWife());
        }
    }
}
//備忘錄:美女
class Girl
{ 
  private String name;
  public Girl(String name)
  { 
      this.name=name; 
  }     
  public void setName(String name)
  { 
      this.name=name; 
  }
  public String getName()
  { 
      return name; 
  }
}
//發(fā)起人:您
class You
{ 
    private String wifeName;    //妻子
    public void setWife(String name)
    { 
        wifeName=name; 
    }
    public String getWife()
    { 
        return wifeName; 
    }
    public Girl createMemento()
    { 
        return new Girl(wifeName); 
    }
    public void restoreMemento(Girl p)
    { 
        setWife(p.getName());
    } 
}
//管理者:美女棧
class GirlStack
{ 
    private Girl girl[];
    private int top;
    GirlStack()
    {
        girl=new Girl[5];
        top=-1;
    }
    public boolean push(Girl p)
    {
        if(top>=4)
        {
            System.out.println("你太花心了践宴,變來變?nèi)サ模?);
            return false;
        }
        else
        {
            girl[++top]=p;
            return true;
        }
    }
    public Girl pop()
    {
        if(top<=0)
        {
            System.out.println("美女棧空了爷怀!");
            return girl[0];
        }
        else return girl[top--]; 
    }
}

程序運(yùn)行結(jié)果如圖 3 所示阻肩。


圖3 相親游戲的運(yùn)行結(jié)果

模式的應(yīng)用場景

  • 需要保存與恢復(fù)數(shù)據(jù)的場景,如玩游戲時的中間結(jié)果的存檔功能运授。
  • 需要提供一個可回滾操作的場景烤惊,如 Word、記事本吁朦、Photoshop柒室,Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵,還有數(shù)據(jù)庫中事務(wù)操作逗宜。

模式的擴(kuò)展

在前面介紹的備忘錄模式中雄右,有單狀態(tài)備份的例子,也有多狀態(tài)備份的例子纺讲。下面介紹備忘錄模式如何同原型模式混合使用擂仍。在備忘錄模式中,通過定義“備忘錄”來備份“發(fā)起人”的信息刻诊,而原型模式的 clone() 方法具有自備份功能防楷,所以,如果讓發(fā)起人實現(xiàn) Cloneable 接口就有備份自己的功能则涯,這時可以刪除備忘錄類复局,其結(jié)構(gòu)圖如圖 4 所示。


圖4 帶原型的備忘錄模式的結(jié)構(gòu)圖

實現(xiàn)代碼如下:

package memento;
public class PrototypeMemento
{
    public static void main(String[] args)
    {
        OriginatorPrototype or=new OriginatorPrototype();
        PrototypeCaretaker cr=new PrototypeCaretaker();       
        or.setState("S0"); 
        System.out.println("初始狀態(tài):"+or.getState());           
        cr.setMemento(or.createMemento()); //保存狀態(tài)      
        or.setState("S1"); 
        System.out.println("新的狀態(tài):"+or.getState());        
        or.restoreMemento(cr.getMemento()); //恢復(fù)狀態(tài)
        System.out.println("恢復(fù)狀態(tài):"+or.getState());
    }
}
//發(fā)起人原型
class OriginatorPrototype  implements Cloneable
{ 
    private String state;     
    public void setState(String state)
    { 
        this.state=state; 
    }
    public String getState()
    { 
        return state; 
    }
    public OriginatorPrototype createMemento()
    { 
        return this.clone(); 
    } 
    public void restoreMemento(OriginatorPrototype opt)
    { 
        this.setState(opt.getState()); 
    }
    public OriginatorPrototype clone()
    {
        try
        {
            return (OriginatorPrototype) super.clone();
        }
        catch(CloneNotSupportedException e)
        {
            e.printStackTrace();
        }
        return null;
    }
}
//原型管理者
class PrototypeCaretaker
{ 
    private OriginatorPrototype opt;       
    public void setMemento(OriginatorPrototype opt)
    { 
        this.opt=opt; 
    }
    public OriginatorPrototype getMemento()
    { 
        return opt; 
    }
}

程序的運(yùn)行結(jié)果如下:

初始狀態(tài):S0
新的狀態(tài):S1
恢復(fù)狀態(tài):S0
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粟判,一起剝皮案震驚了整個濱河市亿昏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌档礁,老刑警劉巖角钩,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡递礼,警方通過查閱死者的電腦和手機(jī)惨险,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脊髓,“玉大人辫愉,你說我怎么就攤上這事〗酰” “怎么了恭朗?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長依疼。 經(jīng)常有香客問我痰腮,道長,這世上最難降的妖魔是什么律罢? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任膀值,我火速辦了婚禮,結(jié)果婚禮上弟翘,老公的妹妹穿的比我還像新娘虫腋。我一直安慰自己,他們只是感情好稀余,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布悦冀。 她就那樣靜靜地躺著,像睡著了一般睛琳。 火紅的嫁衣襯著肌膚如雪盒蟆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天师骗,我揣著相機(jī)與錄音历等,去河邊找鬼。 笑死辟癌,一個胖子當(dāng)著我的面吹牛寒屯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播黍少,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寡夹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了厂置?” 一聲冷哼從身側(cè)響起菩掏,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昵济,沒想到半個月后智绸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體野揪,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年瞧栗,在試婚紗的時候發(fā)現(xiàn)自己被綠了斯稳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沼溜,死狀恐怖平挑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情系草,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布唆涝,位于F島的核電站找都,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏廊酣。R本人自食惡果不足惜能耻,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亡驰。 院中可真熱鬧晓猛,春花似錦、人聲如沸凡辱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽透乾。三九已至洪燥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乳乌,已是汗流浹背捧韵。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留汉操,地道東北人再来。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像磷瘤,于是被迫代替她去往敵國和親芒篷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 定義 備忘錄模式又叫做快照模式(Snapshot Pattern)或Token模式膀斋,是對象的行為模式梭伐。 備忘錄對象...
    步積閱讀 2,937評論 0 2
  • 備忘錄模式捕捉并且具象化一個對象的內(nèi)在狀態(tài)。換句話說仰担,它把你的對象存在了某個地方糊识,然后在以后的某個時間再把它恢復(fù)出...
    S大偉閱讀 366評論 0 0
  • 1.初識備忘錄模式 在不破壞封裝性的前提下绩社,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)赂苗。這樣以后就可將該對...
    王偵閱讀 471評論 0 0
  • 今天昏睡了一天愉耙,直到下午有個朋友要去店里拿東西,整個人懵懵懂懂地爬起來趕去店里拌滋。下午還一直掙扎要不要去店里朴沿,但想想...
    H_韓閱讀 148評論 0 0
  • 昨天沒寫,也沒早起败砂,因為太累赌渣,所以索性就給自己休息一下,睡到八九點昌犹。然后今天又元氣滿滿坚芜。 凌晨操作: 1. 12:...
    灰鴿1號閱讀 244評論 0 1