每個人都有犯錯誤的時候,都希望有種“后悔葯”能彌補自己的過失,讓自己重新開始,但現實是殘酷的。在計算機應用中,客戶同樣會常常犯錯誤,能否提供“后悔葯”給他們呢?當然是可以的,而且是有必要的。這個功能由“備忘錄模式”來實現。
其實很多應用軟件都提供了這項功能,如 Word、記事本、Photoshop、Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵時能撤銷當前操作,使文檔恢復到之前的狀態;還有在 IE 中的后退鍵、數據庫事務管理中的回滾操作、玩游戲時的中間結果存檔功能、數據庫與操作系統的備份操作、棋類游戲中的悔棋功能等都屬於這類。
備忘錄模式能記錄一個對象的內部狀態,當用戶后悔時能撤銷當前操作,使數據恢復到它原先的狀態。 模式的定義與特點備忘錄(Memento)模式的定義:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便以后當需要時能將該對象恢復到原先保存的狀態。該模式又叫快照模式。
備忘錄模式是一種對象行為型模式,其主要優點如下。
- 提供了一種可以恢復狀態的機制。當用戶需要時能夠比較方便地將數據恢復到某個歷史的狀態。
- 實現了內部狀態的封裝。除了創建它的發起人之外,其他對象都不能夠訪問這些狀態信息。
- 簡化了發起人類。發起人不需要管理和保存其內部狀態的各個備份,所有狀態信息都保存在備忘錄中,並由管理者進行管理,這符合單一職責原則。
其主要缺點是:資源消耗大。如果要保存的內部狀態信息過多或者特別頻繁,將會占用比較大的內存資源。 模式的結構與實現備忘錄模式的核心是設計備忘錄類以及用於管理備忘錄的管理者類,現在我們來學習其結構與實現。1. 模式的結構備忘錄模式的主要角色如下。
- 發起人(Originator)角色:記錄當前時刻的內部狀態信息,提供創建備忘錄和恢復備忘錄數據的功能,實現其他業務功能,它可以訪問備忘錄里的所有信息。
- 備忘錄(Memento)角色:負責存儲發起人的內部狀態,在需要的時候提供這些內部狀態給發起人。
- 管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。
備忘錄模式的結構圖如圖 1 所示。
 圖1 備忘錄模式的結構圖
2. 模式的實現備忘錄模式的實現代碼如下:
- 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("初始狀態:"+or.getState());
- cr.setMemento(or.createMemento()); //保存狀態
- or.setState("S1");
- System.out.println("新的狀態:"+or.getState());
- or.restoreMemento(cr.getMemento()); //恢復狀態
- System.out.println("恢復狀態:"+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;
- }
- }
- //發起人
- 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;
- }
- }
程序運行的結果如下:初始狀態:S0新的狀態:S1恢復狀態:S0模式的應用實例【例1】利用備忘錄模式設計相親游戲。
分析:假如有西施、王昭君、貂蟬、楊玉環四大美女同你相親,你可以選擇其中一位作為你的愛人;當然,如果你對前面的選擇不滿意,還可以重新選擇,但希望你不要太花心;這個游戲提供后悔功能,用“備忘錄模式”設計比較合適(點此下載所要顯示的四大美女的圖片)。
首先,先設計一個美女(Girl)類,它是備忘錄角色,提供了獲取和存儲美女信息的功能;然后,設計一個相親者(You)類,它是發起人角色,它記錄當 前時刻的內部狀態信息(臨時妻子的姓名),並提供創建備忘錄和恢復備忘錄數據的功能;最后,定義一個美女棧(GirlStack)類,它是管理者角色,負責對備忘錄進行管理,用於保存相親者(You)前面選過的美女信息,不過最多只能保存 4 個,提供后悔功能。
客戶類設計成窗體程序,它包含美女棧(GirlStack)對象和相親者(You)對象,它實現了 ActionListener 接口的事件處理方法 actionPerformed(ActionEvent e),並將 4 大美女圖像和相親者(You)選擇的美女圖像在窗體中顯示出來。圖 2 所示是其結構圖。
 圖2 相親游戲的結構圖
程序代碼如下:
- 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("利用備忘錄模式設計相親游戲");
- 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("楊玉環");
- 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()); //保存狀態
- }
- //顯示圖片
- void showPicture(String name)
- {
- EastJP.removeAll(); //清除面板內容
- 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()); //保存狀態
- if(ok && girl1.isSelected())
- {
- showPicture("西施");
- }
- else if(ok && girl2.isSelected())
- {
- showPicture("貂蟬");
- }
- else if(ok && girl3.isSelected())
- {
- showPicture("王昭君");
- }
- else if(ok && girl4.isSelected())
- {
- showPicture("楊玉環");
- }
- }
- else if(e.getSource()==button2)
- {
- you.restoreMemento(girls.pop()); //恢復狀態
- 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;
- }
- }
- //發起人:您
- 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("你太花心了,變來變去的!");
- return false;
- }
- else
- {
- girl[++top]=p;
- return true;
- }
- }
- public Girl pop()
- {
- if(top<=0)
- {
- System.out.println("美女棧空了!");
- return girl[0];
- }
- else return girl[top--];
- }
- }
程序運行結果如圖 3 所示。
模式的應用場景前面學習了備忘錄模式的定義與特點、結構與實現,現在來看該模式的以下應用場景。
- 需要保存與恢復數據的場景,如玩游戲時的中間結果的存檔功能。
- 需要提供一個可回滾操作的場景,如 Word、記事本、Photoshop,Eclipse 等軟件在編輯時按 Ctrl+Z 組合鍵,還有數據庫中事務操作。
模式的擴展在前面介紹的備忘錄模式中,有單狀態備份的例子,也有多狀態備份的例子。下面介紹備忘錄模式如何同原型模式混合使用。在備忘錄模式中,通過定義“備忘錄”來備份“發起人”的信息,而原型模式的 clone() 方法具有自備份功能,所以,如果讓發起人實現 Cloneable 接口就有備份自己的功能,這時可以刪除備忘錄類,其結構圖如圖 4 所示。
 圖4 帶原型的備忘錄模式的結構圖
實現代碼如下:
- 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("初始狀態:"+or.getState());
- cr.setMemento(or.createMemento()); //保存狀態
- or.setState("S1");
- System.out.println("新的狀態:"+or.getState());
- or.restoreMemento(cr.getMemento()); //恢復狀態
- System.out.println("恢復狀態:"+or.getState());
- }
- }
- //發起人原型
- 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;
- }
- }
程序的運行結果如下:初始狀態:S0新的狀態:S1恢復狀態:S0 |
|