YAZONG 我的开源

设计模式(二十)行为模式(对象行为型模式)备忘录模式-MEMENTO

 
0 评论0 浏览

2、20 备忘录模式

定义

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

通用类图

image.png

1)Originator 发起人角色

1.1)记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。

1.2)负责创建一个备忘录 Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定 Memento 存储 Originator 的哪些内部状态。

2)Memento 备忘录角色

2.1)负责存储 Originator 发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。

2.2)负责存储 Originator 对象的内部状态,并可防止 Originator 以外的其他对象访问备忘录 Memento。备忘录有两个接口,Caretaker 只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator 能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。

3)Caretaker 备忘录管理员角色

3.1)对备忘录进行管理、保存和提供备忘录。

3.2)负责保存好备忘录 Memento,不能对备忘录的内容进行操作或检查。

通用案例

/**

 * 场景类

 */

public class Client1 {

    public static void main(String[] args) {

        //定义发起人

        Originator originator = new Originator();

        //定义备忘录管理员

        Caretaker caretaker = new Caretaker();

        //创建一个备忘录

        caretaker.setMemento(originator.createMemento());

        //恢复一个备忘录

        originator.restoreMemento(caretaker.getMemento());

    }

}

 

/**

 * 发起人角色

 */

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;

    }

}

克隆方式的备忘录

类图

image.png

案例

/**
 * 场景类
 * TODO 这个案例中程序精简了许多,而且高层模块的依赖也减少了.
 * TODO 原型模式的深拷贝和浅拷贝问题,在复杂的场景下会让程序逻辑异常混乱,出现错误也很难追踪.因此clone方式的备忘录模式适用于较简单的场景.
 * TODO 使用clone方式的备忘录模式,可以使用在比较简单的场景或比较单一的场景中,尽量不要与其他的对象产生严重的耦合关系.
 */
public class Client2 {
    public static void main(String[] args) {
        //定义发起人
        Originator2 originator2 = new Originator2();
        //建立初始状态
        originator2.setState("初始状态");
        System.out.println("初始状态是" + originator2.getState());
        //建立备份
        originator2.createMemento();
        //修改状态
        originator2.setState("修改后的状态");
        System.out.println("修改后的状态是" + originator2.getState());
        //恢复原有状态
        originator2.restoreMemento();
        System.out.println("恢复后的状态是" + originator2.getState());

    }
}

/**
 * 融合备忘录的发起人角色
 * 增加了clone方法,产生了一个备份对象,需要使用的时候再还原.
 */
class Originator implements Cloneable{

    //内部状态
    private String state = "";

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    //创建一个备忘录
    public Originator createMemento(){
        return this.clone();
    }

    //恢复一个备忘录
    public void restoreMemento(Originator _originator){
        this.setState(_originator.getState());
    }

    @Override
    protected Originator clone(){
        try {
            return (Originator)super.clone();
        }catch (CloneNotSupportedException e){
            e.getLocalizedMessage();
        }
        return null;
    }
}

/**
 * 精简:发起人自主备份和恢复
 * TODO 这里和备忘录模式的定义不相符,它定义是"在该对象之外保存这个状态",而这里却把这个状态保存在了发起人内部.
 * TODO 在面向对象设计中,即使把一个类封装在另一个类中也是可以做到的,何况一个小小的对象复制,这是它的设计模式完全没有预见到的,我们把它弥补回来.
 */
class Originator2 implements Cloneable{
    private Originator2 backup;
    //内部状态
    private String state = "";

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    //创建一个备忘录
    public void createMemento(){
        this.backup = this.clone();
    }

    //恢复一个备忘录
    public void restoreMemento(){
        //在进行恢复前应该进行断言,防止空指针.
        this.setState(this.backup.getState());
    }

    @Override
    protected Originator2 clone() {
        try {
            return (Originator2)super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return null;
    }
}
/**
 * 备忘录管理员角色
 * 备忘录角色转换成了发起人角色
 */
class Caretaker{
    //发起人对象
    private Originator originator;

    public Originator getOriginator() {
        return originator;
    }

    public void setOriginator(Originator originator) {
        this.originator = originator;
    }
}


输出结果:
初始状态是初始状态
修改后的状态是修改后的状态
恢复后的状态是初始状态

多状态的备忘录

描述

在实际的开发中,一个对象不可能只有一个状态,一个 javabean 有多个属性非常常见,这都是它的状态。

对象全状态备份方案,有多种处理方式,比如使用 clone 的方式、数据技术(DTO 回写到临时表)中。

此案例中,提供处理多个状态的备份和还原问题。

类图

image.png

案例

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * 场景类
 * TODO 通过这种方式的改造,不管有多少状态都没有问题,直接把原有的对象所有属性都备份了一遍,也可以容易恢复当时的点数据.
 * TODO 如果要设计一个在运行期决定备份状态的框架,则建议采用AOP框架来实现,避免采用动态代理无谓地增加程序逻辑复杂性.
 */
public class Client3 {
    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(ori.createMemento());
        //修改状态值
        ori.setState1("软件");
        ori.setState2("架构");
        ori.setState3("优秀");
        System.out.println("----------修改后状态----------\n" + ori);
        //恢复一个备忘录
        ori.restoreMemento(caretaker.getMemento());
        System.out.println("----------恢复后状态----------\n" + ori);
    }
}

/**
 * 发起人角色
 */
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());
    }

    @Override
    public String toString() {
        return "Originator{" +
                "state1='" + state1 + '\'' +
                ", state2='" + state2 + '\'' +
                ", state3='" + state3 + '\'' +
                '}';
    }
}

/**
 * 备忘录角色
 * TODO 为什么使用HashMap,直接使用Originator对象的拷贝不是一个很好的办法吗?
 * TODO 可以这样做,但这样就破坏了发起人的通用性,在做恢复动作的时候需要对该对象进行多次赋值操作,也容易产生错误.
 */
class Memento{

    //接受HashMap作为状态
    private HashMap<String,Object> stateMap;
    //接受一个对象,建立一个备份
    public Memento(HashMap<String, Object> stateMap) {
        this.stateMap = stateMap;
    }

    public HashMap<String, Object> getStateMap() {
        return stateMap;
    }

    public void setStateMap(HashMap<String, Object> stateMap) {
        this.stateMap = stateMap;
    }

}

/**
 * BeanUtils工具类
 */
class BeanUtils {

    //把bean的所有属性及数值放入到HashMap中
    public static HashMap<String,Object> backupProp(Object bean){
        HashMap<String,Object> result = new HashMap<>();
        try {
            //获得bean描述
            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;
    }

    //把HashMap的值返回到bean中
    public static void restoreProp(Object bean,HashMap<String,Object> propMap){
        try {
            //获得bean属性
            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 Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }

}

----------初始化状态----------
Originator{state1='中国', state2='强盛', state3='繁荣'}
----------修改后状态----------
Originator{state1='软件', state2='架构', state3='优秀'}
----------恢复后状态----------
Originator{state1='中国', state2='强盛', state3='繁荣'}

多备份的备忘录

描述

检查点,就是在备份的时候做的戳记,系统级的备份一般就是时间戳,那这里的案例的检查点,一般是一个有意义的字符串。

类图

image.png

案例

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * 场景类
 * TODO 注意此案例的内存溢出问题,该备份一旦产生就装入内存,没有任何销毁的意向,这是非常危险的.
 * TODO 因此,在设计系统时,要严格限定备忘录的创建,建议增加MAP的上限,否则系统很容易产生内存溢出的情况.
 */
public class Client4 {
    public static void main(String[] args) {
        //定义发起人
        Originator originator = new Originator();
        //定义备忘录管理员
        Caretaker caretaker = new Caretaker();
        //创建两个备忘录
        caretaker.setMemento("001",originator.createMemento());
        caretaker.setMemento("002",originator.createMemento());
        //恢复一个指定标记的备忘录
        originator.restoreMemento(caretaker.getMemento("001"));
    }
}

/**
 * 发起人角色
 */
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());
    }

    @Override
    public String toString() {
        return "Originator{" +
                "state1='" + state1 + '\'' +
                ", state2='" + state2 + '\'' +
                ", state3='" + state3 + '\'' +
                '}';
    }
}

/**
 * 备忘录角色
 * TODO 为什么使用HashMap,直接使用Originator对象的拷贝不是一个很好的办法吗?
 * TODO 可以这样做,但这样就破坏了发起人的通用性,在做恢复动作的时候需要对该对象进行多次赋值操作,也容易产生错误.
 */
class Memento{

    //接受HashMap作为状态
    private HashMap<String,Object> stateMap;
    //接受一个对象,建立一个备份
    public Memento(HashMap<String, Object> stateMap) {
        this.stateMap = stateMap;
    }

    public HashMap<String, Object> getStateMap() {
        return stateMap;
    }

    public void setStateMap(HashMap<String, Object> stateMap) {
        this.stateMap = stateMap;
    }

}

/**
 * BeanUtils工具类
 */
class BeanUtils {

    //把bean的所有属性及数值放入到HashMap中
    public static HashMap<String,Object> backupProp(Object bean){
        HashMap<String,Object> result = new HashMap<>();
        try {
            //获得bean描述
            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;
    }

    //把HashMap的值返回到bean中
    public static void restoreProp(Object bean,HashMap<String,Object> propMap){
        try {
            //获得bean属性
            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 HashMap<String,Memento> memMap = new HashMap<>();
    public Memento getMemento(String idx){
        return memMap.get(idx);
    }
    public void setMemento(String idx,Memento memento){
        this.memMap.put(idx,memento);
    }
}

备忘录案例优化

描述

1)在系统管理上,一个备份的数据是完全、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义。在我们的设计领域中,也存在着同样的问题,备份是不能被篡改的,也就是说需要缩小备份出的备忘录的阅读权限,保证只能是发起人可读就成了。那么这个案例中使用内置类来解决这个问题。

2)在这个案例中,我们使用了一个新的设计方法:双接口设计。我们的一个类可以实现多个接口,在系统设计时,如果考虑对象的安全问题,则可以提供两个接口,一个是业务的正常接口,实现必要的业务逻辑,叫做宽接口;另外一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,比如容器对象,这个叫做窄接口,由于窄接口中没有提供任何操纵数据的方法,因此相对来说比较安全。

类图

image.png

案例

/**
 * 场景类
 * TODO 在这个案例中,我们使用了一个新的设计方法:双接口设计。
 * TODO 我们的一个类可以实现多个接口,在系统设计时,如果考虑对象的安全问题,则可以提供两个接口,
 * TODO 一个是业务的正常接口,实现必要的业务逻辑,叫做宽接口;
 * TODO 另外一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,比如容器对象,这个叫做窄接口,由于窄接口中没有提供任何操纵数据的方法,因此相对来说比较安全。
 */
public class Client5 {
    public static void main(String[] args) {
        //定义发起人
        Originator originator = new Originator();
        //设置状态
        originator.setState("1");
        IMemento memento = originator.createMemento();
        //定义备忘录管理员
        Caretaker caretaker = new Caretaker();
        //创建备忘录
        caretaker.setMemento(memento);
        System.out.println(caretaker.getMemento());
        //恢复备忘录
        originator.restoreMemento(caretaker.getMemento());
    }
}

/**
 * 备忘录管理者
 */
class Caretaker{
    //备忘录对象
    private IMemento memento;

    public IMemento getMemento() {
        return memento;
    }

    public void setMemento(IMemento memento) {
        this.memento = memento;
    }
}

/**
 * 备忘录的空接口(窄接口)
 * TODO Originator的内置类Memento全部都是private的访问权限,也就是说除了发起人外,别人休想访问到.
 * TODO 那么要产生关联关系,就通过这个空接口就好了,这个空接口有公共的访问权限.
 * TODO 全部通过接口访问,当然没有问题,如果想访问它的属性那是肯定不行的.
 * TODO 但是安全是相对的,没有绝对的安全,可以使用reflect反射修改Memento的数据.
 */
interface IMemento{

}
/**
 * 发起人角色
 */
class Originator{
    //内部状态
    private String state = "";

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    //创建一个备忘录
    public IMemento createMemento(){
        return new Memento(this.state);
    }

    //恢复一个备忘录(注意看这里)
    public void restoreMemento(IMemento memento){
        this.setState(((Memento)memento).getState());
    }

    //内置类
    private class Memento implements IMemento{
        //发起人的内部状态
        private String state = "";
        //构造函数传递参数
        private Memento(String state) {
            this.state = state;
        }
        private String getState() {
            return state;
        }
        private void setState(String state) {
            this.state = state;
        }
    }

}

使用场景

1)需要保存和恢复数据的相关状态场景。

2)提供一个可回滚的操作。

3)需要监控的副本场景中。

4)数据库连接的事物管理。比如实现 JDBC 驱动的事物处理。

5)其他案例:在设计的时候,不要使用数据库的临时表作为缓存备份数据了,虽然是一个简单的办法,但是它加大了数据库操作的频繁度,把压力下放到数据库了,最好的解决办法就是使用备忘录模式。

注意事项

1)备忘录的生命周期

备忘录创建出来就要在”最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用及要立刻删除其引用,等待垃圾回收器对它的回收处理。

2)备忘录的性能

不要在频繁建立备份的场景中使用备忘录模式(比如一个 for 循环中)。一是控制不了备忘录建立的对象数量,二是大对象的建立是要消耗资源的,系统的性能需要考虑。


标题:设计模式(二十)行为模式(对象行为型模式)备忘录模式-MEMENTO
作者:yazong
地址:https://blog.llyweb.com/articles/2020/09/05/1599318429796.html