備忘錄模式(Memento)
每個人都有犯錯誤的時候,都希望有種“后悔藥”能彌補(bǔ)自己的過失,讓自己重新開始,但現(xiàn)實(shí)是殘酷的。在計算機(jī)應(yīng)用中,客戶同樣會常常犯錯誤,能否提供“后悔藥”給他們呢?當(dāng)然是可以的,而且是有必要的。這個功能由“備忘錄模式”來實(shí)現(xiàn)。
其實(shí)很多應(yīng)用軟件都提供了這項(xià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)。
模式的定義與特點(diǎn)
-
備忘錄(Memento)模式的定義:
在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài),以便以后當(dāng)需要時能將該對象恢復(fù)到原先保存的狀態(tài)。該模式又叫快照模式。 - 備忘錄模式(Memento)模式的優(yōu)點(diǎn):
- 提供了一種可以恢復(fù)狀態(tài)的機(jī)制。當(dāng)用戶需要時能夠比較方便地將數(shù)據(jù)恢復(fù)到某個歷史的狀態(tài)。
- 實(shí)現(xiàn)了內(nèi)部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外,其他對象都不能夠訪問這些狀態(tài)信息。
- 簡化了發(fā)起人類。發(fā)起人不需要管理和保存其內(nèi)部狀態(tài)的各個備份,所有狀態(tài)信息都保存在備忘錄中,并由管理者進(jìn)行管理,這符合單一職責(zé)原則。
-
備忘錄模式(Memento)模式的缺點(diǎn):
資源消耗大。如果要保存的內(nèi)部狀態(tài)信息過多或者特別頻繁,將會占用比較大的內(nèi)存資源。
模式的結(jié)構(gòu)與實(shí)現(xiàn)
備忘錄模式的核心是設(shè)計備忘錄類以及用于管理備忘錄的管理者類,現(xiàn)在我們來學(xué)習(xí)其結(jié)構(gòu)與實(shí)現(xiàn)。
1. 模式的結(jié)構(gòu)
備忘錄模式的主要角色如下。
- 發(fā)起人(Originator)角色: 記錄當(dāng)前時刻的內(nèi)部狀態(tài)信息,提供創(chuàng)建備忘錄和恢復(fù)備忘錄數(shù)據(jù)的功能,實(shí)現(xiàn)其他業(yè)務(wù)功能,它可以訪問備忘錄里的所有信息。
- 備忘錄(Memento)角色: 負(fù)責(zé)存儲發(fā)起人的內(nèi)部狀態(tài),在需要的時候提供這些內(nèi)部狀態(tài)給發(fā)起人。
- 管理者(Caretaker)角色: 對備忘錄進(jìn)行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內(nèi)容進(jìn)行訪問與修改。
備忘錄模式的結(jié)構(gòu)圖如圖 1 所示。

2. 模式的實(shí)現(xiàn)
備忘錄模式的實(shí)現(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í)例
【例】利用備忘錄模式設(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)對象,它實(shí)現(xiàn)了 ActionListener 接口的事件處理方法 actionPerformed(ActionEvent e),并將 4 大美女圖像和相親者(You)選擇的美女圖像在窗體中顯示出來。圖 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 所示。

模式的應(yīng)用場景
- 需要保存與恢復(fù)數(shù)據(jù)的場景,如玩游戲時的中間結(jié)果的存檔功能。
- 需要提供一個可回滾操作的場景,如 Word、記事本、Photoshop,Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵,還有數(shù)據(jù)庫中事務(wù)操作。
模式的擴(kuò)展
在前面介紹的備忘錄模式中,有單狀態(tài)備份的例子,也有多狀態(tài)備份的例子。下面介紹備忘錄模式如何同原型模式混合使用。在備忘錄模式中,通過定義“備忘錄”來備份“發(fā)起人”的信息,而原型模式的 clone() 方法具有自備份功能,所以,如果讓發(fā)起人實(shí)現(xiàn) Cloneable 接口就有備份自己的功能,這時可以刪除備忘錄類,其結(jié)構(gòu)圖如圖 4 所示。

實(shí)現(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