備忘錄模式(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)點:
- 提供了一種可以恢復(fù)狀態(tài)的機(jī)制志于。當(dāng)用戶需要時能夠比較方便地將數(shù)據(jù)恢復(fù)到某個歷史的狀態(tài)涮因。
- 實現(xiàn)了內(nèi)部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外伺绽,其他對象都不能夠訪問這些狀態(tài)信息养泡。
- 簡化了發(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)
備忘錄模式的主要角色如下。
- 發(fā)起人(Originator)角色: 記錄當(dāng)前時刻的內(nèi)部狀態(tài)信息乔妈,提供創(chuàng)建備忘錄和恢復(fù)備忘錄數(shù)據(jù)的功能蝙云,實現(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. 模式的實現(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)圖艾船。
程序代碼如下:
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ā)起人實現(xiàn) Cloneable 接口就有備份自己的功能则涯,這時可以刪除備忘錄類复局,其結(jié)構(gòu)圖如圖 4 所示。
實現(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