当前位置: 首页 > news >正文

设计模式之备忘录模式详解(Memento Pattern)

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

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

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

认识备忘录模式

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

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

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

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

原理类图与角色说明

备忘录模式的原理类图

角色说明

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

模式的应用场景

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

模式的实现

//发起者
public class Originator {
    private String state;

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

    public String getState() {
        return state;
    }

    //保存一个状态对象 Memento
    public Memento saveMemento() {
        return new Memento(state);
    }

    //通过备忘录对象,恢复状态
    public void getStateFromMemento(Memento memento) {
        state = memento.getState();
    }
}
//备忘录
public class Memento {
    private String state;

    public String getState() {
        return state;
    }

    public Memento(String state) {
        this.state = state;
    }
}
//管理者
public class Caretaker {
    //在 List 集合中会有很多的备忘录对象
    private List<Memento> mementoList = new ArrayList<Memento>();

    public void add(Memento memento) {
        mementoList.add(memento);
    }

    //获取到第 index 个 Originator 的 备忘录对象(即保存状态)
    public Memento get(int index) {
        return mementoList.get(index);
    }
}
//客户端测试
public class Client {
    public static void main(String[] args) {
        Caretaker caretaker = new Caretaker();
        Originator originator = new Originator();
        originator.setState("状态#1  攻击力 100");
        caretaker.add(originator.saveMemento());

        originator.setState("状态#1  攻击力 80");
        caretaker.add(originator.saveMemento());

        originator.setState("状态#1  攻击力 50");
        caretaker.add(originator.saveMemento());

        System.out.println("当前的状态是:"+originator.getState());
        //希望得到状态 1, 将 originator 恢复到状态 1
        originator.getStateFromMemento(caretaker.get(0));
        System.out.println("当前的状态是:"+originator.getState());

		//希望得到状态 2, 将 originator 恢复到状态 2
        originator.getStateFromMemento(caretaker.get(1));
        System.out.println("当前的状态是:"+originator.getState());
    }
}
具体输出如下
当前的状态是:状态#3  攻击力 50
当前的状态是:状态#1  攻击力 100
当前的状态是:状态#2  攻击力 80

备忘录模式的应用实例

下面以游戏角色状态为例应用备忘录模式:

游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战 Boss 后攻击力和防御力下降,从备忘录对象恢复到大战前的状态

实例图解:
游戏实例类图

创建发起者:游戏角色
public class GameRole {
    private int vit;//攻击力
    private int def;//防御力

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }

    //创建 Memento ,即根据当前的状态得到 Memento
    public Memento createMemento() {
        return new Memento(vit,def);
    }

    //从备忘录对象,恢复 GameRole 的状态
    public void recoverGameRoleFromMemento(Memento memento) {
        this.vit = memento.getVit();
        this.def = memento.getDef();
    }

    //显示当前游戏角色的状态
    public void display() {
        System.out.println("游戏角色当前的攻击力:" + this.vit + "  防御力: " + this.def);
    }
}
创建备忘录:memento
public class Memento {
    private int vit; //攻击力
    private int def; //防御力

    public Memento(int vit, int def) {
        this.vit = vit;
        this.def = def;
    }

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }
}
创建管理者:caretaker
//管理者对象, 保存游戏角色的状态
public class CareTaker {
    private Memento memento;//如果只保存一次状态
//    private List<Memento> mementos;//如果对一个对象保存多个状态
//    private HashMap<String, ArrayList<Memento>> rolesMementos;//如果对多个对象保存多个状态

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
客户端测试
public class Client {
    public static void main(String[] args) {
        //创建管理者
        CareTaker careTaker = new CareTaker();
        //创建发起人:游戏角色
        GameRole gameRole = new GameRole();
        gameRole.setVit(100);
        gameRole.setDef(100);
        //创建备忘录,保存游戏角色开始时的状态
        Memento memento = gameRole.createMemento();
        gameRole.display();

        System.out.println("大战boss中......");
        gameRole.setVit(50);
        gameRole.setDef(50);
        gameRole.display();

        System.out.println("恢复大战boss前的状态");
        gameRole.recoverGameRoleFromMemento(memento);
        gameRole.display();
    }
}
程序的运行结果如下:
游戏角色当前的攻击力:100  防御力: 100
大战boss中......
游戏角色当前的攻击力:50  防御力: 50
恢复大战boss前的状态
游戏角色当前的攻击力:100  防御力: 100

模型的扩展

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

//发起人原型
public class OriginatorPrototype implements Cloneable{
    private String state;

    public String getState() {
        return state;
    }

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

    public OriginatorPrototype createMemento() {
        return this.clone();
    }

    public void recoverMemento(OriginatorPrototype opt) {
        this.setState(opt.getState());
    }

    public OriginatorPrototype clone() {
        try {
            return (OriginatorPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
//原型管理者
public class PrototypeCaretaker {
    private OriginatorPrototype opt;

    public void setOpt(OriginatorPrototype opt) {
        this.opt = opt;
    }

    public OriginatorPrototype getOpt() {
        return opt;
    }
}
public class Client {
    public static void main(String[] args) {
        PrototypeCaretaker cr = new PrototypeCaretaker();
        OriginatorPrototype or = new OriginatorPrototype();

        or.setState("S0");
        System.out.println("初始状态:" + or.getState());
        cr.setOpt(or.createMemento());//保存状态

        or.setState("S1");
        System.out.println("新的状态:" + or.getState());

        or.recoverMemento(cr.getOpt());//恢复状态
        System.out.println("恢复状态:" + or.getState());
    }
}

程序的运行结果如下:

初始状态:S0
新的状态:S1
恢复状态:S0

相关文章:

  • 从asp转asp.net的相关
  • 设计模式之状态模式详解(State Pattern)
  • 浅谈DataSet 的用法
  • 多维数组和矩阵之顺时针打印二维数组
  • 各种数据库连接代码(JSP)
  • 多维数组与矩阵之0所在的行列清零
  • 多维数组与矩阵之之字形打印矩阵
  • 生姜有味的调色板
  • 设计模式之策略者模式详解(Strategy Pattern)
  • 利用J2ME里的RMS对记录进行排序
  • 设计模式之职责链(责任链)模式(ResponsibilityChain Pattern)
  • SWT GC的drawLine方法的一个隐藏Bug
  • 多维数组与矩阵之子数组的最大累加和
  • 游戏也是软件,J2ME游戏程序员不能忘本
  • 多维数组与矩阵之子矩阵的最大累加和
  • ES2017异步函数现已正式可用
  • JavaScript函数式编程(一)
  • JavaScript学习总结——原型
  • leetcode讲解--894. All Possible Full Binary Trees
  • php的插入排序,通过双层for循环
  • React中的“虫洞”——Context
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • supervisor 永不挂掉的进程 安装以及使用
  • Wamp集成环境 添加PHP的新版本
  • 大型网站性能监测、分析与优化常见问题QA
  • 当SetTimeout遇到了字符串
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 让你的分享飞起来——极光推出社会化分享组件
  • 收藏好这篇,别再只说“数据劫持”了
  • 手写双向链表LinkedList的几个常用功能
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • Prometheus VS InfluxDB
  • 移动端高清、多屏适配方案
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (万字长文)Spring的核心知识尽揽其中
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • .bat批处理出现中文乱码的情况
  • .dwp和.webpart的区别
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .NET4.0并行计算技术基础(1)
  • .NetCore 如何动态路由
  • .NetCore部署微服务(二)
  • .NET中两种OCR方式对比
  • .NET中使用Protobuffer 实现序列化和反序列化
  • @RestController注解的使用
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [C++] cout、wcout无法正常输出中文字符问题的深入调查(1):各种编译器测试
  • [CISCN2019 华北赛区 Day1 Web5]CyberPunk --不会编程的崽
  • [CSS]浮动