设计模式学习笔记(二十二:备忘录模式)

1.1概述

    在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。这就是备忘录模式的定义。

  对象的状态依赖于它的变量的取值情况,对象在程序运行期间的各个时刻可能具有不同的状态。在某些应用中,程序可能需要使用一种合理的方式来保存对象在某一时刻的状态,以便在需要时,对象能恢复到原先保存的状态。在备忘录模式中,称需要保存状态的对象为“原发者”,称负责保存原发者状态的对象为“备忘录”,称负责管理备忘录的对象为“负责人”。备忘录模式要求原发者可以访问备忘录中的细节,即可以访问备忘录中的数据,以便恢复原发者的状态,而负责人只能保存和得到备忘录,但访问备忘录中的数据受到一定的限制。备忘录模式使原发者可以将自己的状态暴露给备忘录,但其他对象想要获得备忘录中的数据会受到一定的限制,这就保证了原发者暴露内部数据的同时,又保证了数据的封装性。另外,经过精心设计的备忘录在保存原发者状态时,可能只需要保存原发者的部分变量即可,也就是备忘录通过保存原发者状态中最本质的数据,就能使原发者根据此备忘录中的数据恢复原始状态。

  例如,对于一个游戏软件,该游戏可能需要经过许多关卡才能最后成功,那么该游戏应当提供保存“游戏关卡”的功能,使游戏玩者在成功完成游戏的某一个关卡之后,保存当前的游戏状态,当玩到下一个关卡失败时,可以选择游戏从上一次保存的状态开始,即从上一次成功后的关卡开始,而不是再从第1关开始。

1.2模式的结构

备忘录模式包括以下三种角色:

(1)原发者(Originator):需要在某个时刻保存其状态的对象。原发者负责创建备忘录,比如使用createMemento()方法创建一个备忘录,然后原发者使用该备忘录记录自己的状态。当原发者需要恢复到某个状态时,它通过获取备忘录中相应的数据来恢复到那一时刻的状态,比如原发者调用restoreFromMemento(Memento mem)方法,并通过参数mem指定备忘录恢复状态。

(2)备忘录(Memento):负责存储原发者状态的对象,创建备忘录的类和创建原发者的类在同一个包中,该类提供的访问数据的方法都是友好方法,使得只有原发者在用一个包中的类的实例才可以访问备忘录中的数据。

(3)负责人(Caretaker):负责管理保存备忘录中的对象。负责人如果不和原发者在同一个包中就不能对备忘录中的内容进行修改或读取。如果需要将备忘录保存到磁盘,负责人可以使用对象流将备忘录写入文件。

备忘录模式结构的类图如下所示:

 

图一:备忘录模式的类图

 

 

1.3备忘录模式的优点

1)备忘录模式使用备忘录可以把原发者的内部状态保存起来,使只有很“亲密的”对象可以访问备忘录中的数据。

2)备忘录模式强调了类设计单一责任原则,即将状态的刻画和保存分开。

 

 

1.4适合使用备忘录模式的情景

1)必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复该对象先前的状态。

2)一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己的内部状态。

 

1.5备忘录模式的使用

使用备忘录模式来设计一个GUI程序,主要功能如下:

  • 程序的窗体中有一个标签组件,用户在标签上单击鼠标左键可以在标签上随机显示一个汉字。但标签上只保留最后一次单击左键所显示的汉字。
  • 程序提供undo操作。当用户在标签上单击鼠标右键时,将取消用户最近一次单击鼠标左键所产生的操作效果,即将标签上的汉字恢复为上一次单击鼠标左键所得到的汉字。用户多次单击鼠标右键来依次取消单击鼠标左键所产生的操作效果。(对于功能2,在实例代码中出现了一点问题,即点击鼠标右键没有任何反应,问题待解决...

  首先看一下本实例构建框架具体类和1.2模式的结构中类图的对应关系,如下图所示:

(1)原发者(Originator)和备忘录(Memento

本程序中,原发者是UnicodeLable类的实例,UnicodeLable类是java.swing 包中JLabel类的子类,包含Integer对象,该对象中的int值代表一个汉字在Unicode表中的位置。备忘录是UnicodeLable类的内部类,因此,只有UnicodeLable类的实例可以访问备忘录中的数据,其他类无法获得备忘录中的数据,UnicodeLable类代码如下:

package com.liuzhen.twenty_two_memento;

import java.awt.Font;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JLabel;
import javax.swing.SwingConstants;

@SuppressWarnings("serial")
public class UnicodeLable extends JLabel{
    private Integer m;
    
    public UnicodeLable(){
        setFont(new Font("宋体",Font.BOLD,100));
        setHorizontalAlignment(SwingConstants.CENTER);
        m = new Integer(19968);
        setText(""+(char)m.intValue());
        addMouseListener(new MouseAdapter(){
            public void mouseReleased(MouseEvent e){
                if(e.getModifiers() == InputEvent.BUTTON1_MASK){  //释放左键
                    m = (int)(Math.random()*1000+19968);
                    setText(""+(char)m.intValue());
                }
                
            }
        });
    }
    
    public Memento createMemento(){
        Memento mem = new Memento();
        mem.setState(m);
        return mem;
    }
    
    public void restoreFromMemento(Memento mem){
        m = mem.getState();
        if(m != null)
            setText(""+(char)m.intValue());
    }
    
    public class Memento{    //Memento是UnicodeLabel中的内部类
        private Integer m;
        private void setState(Integer m){
            this.m = m;
        }
        private Integer getState(){
            return m;
        }
    }
}

 

(2)负责人(Caretaker

对于本问题,负责人是Caretaker类。Caretaker类使用一个堆栈来存放备忘录,当用户需要undo操作时,从堆栈弹出最近一次的备忘录给用户,用户用该备忘录恢复原发者的状态,当堆栈为空时,用户不能进行ubdo操作。Caretaker类的代码如下:

package com.liuzhen.twenty_two_memento;

import java.util.Stack;

public class Caretaker {
    Stack<UnicodeLable.Memento> stack;
    
    Caretaker(){
        stack = new Stack<UnicodeLable.Memento>();
    }
    
    public UnicodeLable.Memento getMemento(){
        if(!(stack.isEmpty())){
            UnicodeLable.Memento memento = stack.pop();
            return memento;
        }
        else
            return null;
    }
    
    public void saveMemento(UnicodeLable.Memento memento){
        stack.push(memento);
    }
}

 

3)具体使用

 通过Twenty_twoApplication类来具体实现上述相关类和接口,来实现备忘录模式的运用,其代码如下:

package com.liuzhen.twenty_two_memento;

import java.awt.BorderLayout;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JLabel;

@SuppressWarnings("serial")
public class Twenty_twoApplication extends JFrame implements MouseListener{
    UnicodeLable label;
    Caretaker caretaker;           //负责人
    
    Twenty_twoApplication(){
        label = new UnicodeLable();
        label.addMouseListener(this);
        add(new JLabel("单击左键显示一个汉字,单击右键撤销单击左键的操作效果"),BorderLayout.NORTH);
        add(label,BorderLayout.CENTER);
        caretaker = new Caretaker();   //创建负责人
    }
    
    public void mousePressed(MouseEvent e){
        if(e.getModifiers() == InputEvent.BUTTON1_DOWN_MASK){   //按下左键
            caretaker.saveMemento(label.createMemento());   //保存备忘录
        }
        if(e.getModifiers() == InputEvent.BUTTON3_DOWN_MASK){    //按下右键
            UnicodeLable.Memento memento = caretaker.getMemento();   //得到备忘录
            
            if(memento != null){
                label.restoreFromMemento(memento);    //使用备忘录恢复状态
            }
        }
    }
    
    public void mouseReleased(MouseEvent e){}
    public void mouseEntered(MouseEvent e){}
    public void mouseExited(MouseEvent e){}
    public void mouseClicked(MouseEvent e){}
    
    public static void main(String[] args){
        Twenty_twoApplication win = new Twenty_twoApplication();
        win.setBounds(10,10,300,300);
        win.setVisible(true);
        win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    }
}

运行结果截图:

参考资料:

      1.Java设计模式/耿祥义,张跃平著.——北京:清华大学出版社,2009.5

原文地址:https://www.cnblogs.com/liuzhen1995/p/6047015.html