设计模式系列28—备忘录模式

1 备忘录模式的定义与特点

每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。

其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态

1.1 定义

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式

1.2 结构

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现,备忘录模式的主要角色如下

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录模式的结构图如图所示。
image.png

2 备忘录模式的优缺点

2.1 优点

备忘录模式是一种对象行为型模式,其主要优点如下:

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人的类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

2.2 缺点

其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

3 备忘录模式的使用场景

前面学习了备忘录模式的定义与特点、结构与实现,现在来看该模式的以下应用场景。

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

4 备忘录模式的实现

4.1 单状态单备份备忘录

//发起人
class Originator {  
       private String state = "";  
         
       public String getState() {  
           return state;  
       }  
       public void setState(String state) {  
           this.state = state;  
       }  
       public Memento createMemento(){  
           return new Memento(this.state);  
       }  
       public void restoreMemento(Memento memento){  
           this.setState(memento.getState());  
       }  
   }  
//备忘录
class Memento {  
       private String state = "";  
       public Memento(String state){  
           this.state = state;  
       }  
       public String getState() {  
           return state;  
       }  
       public void setState(String state) {  
           this.state = state;  
       }  
   } 
//管理者 
class Caretaker {  
       private Memento memento;  
       public Memento getMemento(){  
           return memento;  
       }  
       public void setMemento(Memento memento){  
           this.memento = memento;  
       }  
   }  
//客户端
public class Client {  
       public static void main(String[] args){  
           Originator originator = new Originator();  
           originator.setState("状态1");  
           System.out.println("初始状态:"+originator.getState());  
           Caretaker caretaker = new Caretaker();  
           caretaker.setMemento(originator.createMemento());  
           originator.setState("状态2");  
           System.out.println("改变后状态:"+originator.getState());  
           originator.restoreMemento(caretaker.getMemento());  
           System.out.println("恢复后状态:"+originator.getState());  
       }  
   }

代码演示了一个单状态单备份的例子,逻辑非常简单:Originator类中的state变量需要备份,以便在需要的时候恢复;Memento类中,也有一个state变量,用来存储Originator类中state变量的临时状态;而Caretaker类就是用来管理备忘录类的,用来向备忘录对象中写入状态或者取回状态。

4.1 多状态多备份备忘录

通用代码演示的例子中,Originator类只有一个state变量需要备份,而通常情况下,发起人角色通常是一个javaBean,对象中需要备份的变量不止一个,需要备份的状态也不止一个,这就是多状态多备份备忘录。

实现备忘录的方法很多,备忘录模式有很多变形和处理方式,像通用代码那样的方式一般不会用到,多数情况下的备忘录模式,是多状态多备份的。其实实现多状态多备份也很简单,最常用的方法是,我们在Memento中增加一个Map容器来存储所有的状态,在Caretaker类中同样使用一个Map容器才存储所有的备份。下面我们给出一个多状态多备份的例子:

//发起人
class Originator {  
       private String state1 = "";  
       private String state2 = "";  
       private String state3 = "";  
     
       public String getState1() {  
           return state1;  
       }  
       public void setState1(String state1) {  
           this.state1 = state1;  
       }  
       public String getState2() {  
           return state2;  
       }  
       public void setState2(String state2) {  
           this.state2 = state2;  
       }  
       public String getState3() {  
           return state3;  
       }  
       public void setState3(String state3) {  
           this.state3 = state3;  
       }  
       public Memento createMemento(){  
           return new Memento(BeanUtils.backupProp(this));  
       }  
         
       public void restoreMemento(Memento memento){  
           BeanUtils.restoreProp(this, memento.getStateMap());  
       }  
       public String toString(){  
           return "state1="+state1+"state2="+state2+"state3="+state3;  
       }  
   }  
//备忘录
class Memento {  
       private Map<String, Object> stateMap;  
         
       public Memento(Map<String, Object> map){  
           this.stateMap = map;  
       }  
     
       public Map<String, Object> getStateMap() {  
           return stateMap;  
       }  
     
       public void setStateMap(Map<String, Object> stateMap) {  
           this.stateMap = stateMap;  
       }  
   }  


class BeanUtils { 
	//备份状态 
       public static Map<String, Object> backupProp(Object bean){  
           Map<String, Object> result = new HashMap<String, Object>();  
           try{  
               BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());  
               PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();  
               for(PropertyDescriptor des: descriptors){  
                   String fieldName = des.getName();  
                   Method getter = des.getReadMethod();  
                   Object fieldValue = getter.invoke(bean, new Object[]{});  
                   if(!fieldName.equalsIgnoreCase("class")){  
                       result.put(fieldName, fieldValue);  
                   }  
               }  
                 
           }catch(Exception e){  
               e.printStackTrace();  
           }  
           return result;  
       }  
        //恢复状态
	public static void restoreProp(Object bean, Map<String, Object> propMap){  
           try {  
               BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());  
               PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();  
               for(PropertyDescriptor des: descriptors){  
                   String fieldName = des.getName();  
                   if(propMap.containsKey(fieldName)){  
                       Method setter = des.getWriteMethod();  
                       setter.invoke(bean, new Object[]{propMap.get(fieldName)});  
                   }  
               }  
           } catch (Exception e) {  
               e.printStackTrace();  
           }  
       }  
   }  
//管理者
class Caretaker {  
       private Map<String, Memento> memMap = new HashMap<String, Memento>();  
       public Memento getMemento(String index){  
           return memMap.get(index);  
       }  
         
       public void setMemento(String index, Memento memento){  
           this.memMap.put(index, memento);  
       }  
   }  
//客户端
class Client {  
       public static void main(String[] args){  
           Originator ori = new Originator();  
           Caretaker caretaker = new Caretaker();  
           ori.setState1("中国");  
           ori.setState2("强盛");  
           ori.setState3("繁荣");  
           System.out.println("===初始化状态===\n"+ori);  
             
           caretaker.setMemento("001",ori.createMemento());  
           ori.setState1("软件");  
           ori.setState2("架构");  
           ori.setState3("优秀");  
           System.out.println("===修改后状态===\n"+ori);  
             
           ori.restoreMemento(caretaker.getMemento("001"));  
           System.out.println("===恢复后状态===\n"+ori);  
       }  
   }

5 备忘录模式的常见问题

5.1 备忘录模式的扩展

在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,其结构图如图所示。
image.png

实现代码如下:

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
更新时间:2020-09-08 15:10:19

本文由 清水河恶霸 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:http://ql.magic-seven.top/2020/09/08/设计模式系列28备忘录模式.html
最后更新:2020-09-08 15:10:19

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×