《图解设计模式》读书笔记8-2 MEMENTO模式
Memento模式
备忘录模式最常见的应用是各种编辑器,如果写错了,点击“撤销”按钮就能回到原来的状态。
不使用备忘录模式对实例进行保存和恢复,很容易破坏封装性:将依赖实例内部结构的代码写得到处都是,程序变得难以维护。
备忘录模式专门添加了Memento角色,这个角色专门用来保存和恢复实例,能有效防止对象的封装性遭破坏。
示例代码
下面这段代码演示备忘录模式的用法,代码的主要功能是:
模拟一个掷骰子游戏,规则:
- 结果为1,加100块钱
- 结果为2,减一半钱
- 结果为6,获得一个水果
- 结果为其他,什么也没有
如果钱增加了,则使用备忘录记下来
如果钱连续两次减少,则恢复到第一次减钱之前的状态
程序类图

代码
Memento
public class Memento {
    int money;                              // 所持金钱
    ArrayList fruits;                       // 当前获得的水果
    public int getMoney() {                 // 获取当前所持金钱(narrow interface)
        return money;
    }
    Memento(int money) {                    // 构造函数(wide interface)
        this.money = money;
        this.fruits = new ArrayList();
    }
    void addFruit(String fruit) {           // 添加水果(wide interface)
        fruits.add(fruit);
    }
    List getFruits() {                      // 获取当前所持所有水果(wide interface)
         //浅复制,只复制引用。
         return (List)fruits.clone();
    }
}
Gamer
public class Gamer {
    private int money;                          // 所持金钱
    private List fruits = new ArrayList();      // 获得的水果
    private Random random = new Random();       // 随机数生成器
    private static String[] fruitsname = {      // 表示水果种类的数组
        "苹果", "葡萄", "香蕉", "橘子",
    };
    public Gamer(int money) {                   // 构造函数
        this.money = money;
    }
    public int getMoney() {                     // 获取当前所持金钱
        return money;
    }
    public void bet() {                         // 投掷骰子进行游戏
        int dice = random.nextInt(6) + 1;           // 掷骰子
        if (dice == 1) {                            // 骰子结果为1…增加所持金钱
            money += 100;
            System.out.println("所持金钱增加了。");
        } else if (dice == 2) {                     // 骰子结果为2…所持金钱减半
            money /= 2;
            System.out.println("所持金钱减半了。");
        } else if (dice == 6) {                     // 骰子结果为6…获得水果
            String f = getFruit();
            System.out.println("获得了水果(" + f + ")。");
            fruits.add(f);
        } else {                                    // 骰子结果为3、4、5则什么都不会发生
            System.out.println("什么都没有发生。");
        }
    }
    public Memento createMemento() {                // 拍摄快照
        Memento m = new Memento(money);
        Iterator it = fruits.iterator();
        while (it.hasNext()) {
            String f = (String)it.next();
            m.addFruit(f);
        }
        return m;
    }
    public void restoreMemento(Memento memento) {   // 撤销
        this.money = memento.money;
        this.fruits = memento.getFruits();
    }
    public String toString() {                      // 用字符串表示主人公状态
        return "[money = " + money + ", fruits = " + fruits + "]";
    }
    private String getFruit() {
        return fruitsname[random.nextInt(fruitsname.length)];
    }
}
Main
public class Main {
    public static void main(String[] args) {
        Gamer gamer = new Gamer(100);               // 最初的所持金钱数为100
        Memento memento = gamer.createMemento();    // 保存最初的状态
        for (int i = 0; i < 100; i++) {
            System.out.println("==== " + i);        // 显示掷骰子的次数
            System.out.println("当前状态:" + gamer);    // 显示主人公现在的状态
            gamer.bet();    // 进行游戏 
            System.out.println("所持金钱为" + gamer.getMoney() + "元。");
            // 决定如何处理Memento
            if (gamer.getMoney() > memento.getMoney()) {
                System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
                System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
                gamer.restoreMemento(memento);
            }
            // 等待一段时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("");
        }
    }
}
结果
- 连续两次减钱,则恢复到第1次减钱之前
- 如果加钱,则记录状态
...省略
==== 8
当前状态:[money = 100, fruits = [橘子]]
所持金钱增加了。
所持金钱为200元。
    (所持金钱增加了许多,因此保存游戏当前的状态)
==== 9
当前状态:[money = 200, fruits = [橘子]]
所持金钱减半了。
所持金钱为100元。
==== 10
当前状态:[money = 100, fruits = [橘子]]
所持金钱减半了。
所持金钱为50元。
    (所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 11
当前状态:[money = 200, fruits = [橘子]]
获得了水果(葡萄)。
所持金钱为200元。
...省略
角色和类图
模式类图

角色
- Originator(生成者) - 主要关注 - createMemento和- restoreMemento两个方法,前者是保存快照,后者是恢复快照。本例中由Gamer扮演此角色。
- Memento(备忘录) - 这个角色用来保存Originator的信息,我们要注意其方法的访问权限控制。 - 这个角色内部有宽接口和窄接口两种接口,宽和窄指的是内部结构的对外暴露程度。宽接口只能提供给Originator,窄接口提供给外部角色。 - 这样控制的目的是防止使用者破坏封装。 - 在Originator和Memento之间不存在什么封装,他们俩也深深耦合。这无法避免,他们俩都在围绕着共同的属性集做文章,事实上他们俩本可以合为一体。那为什么拆出来?因为职责分离。 
- Caretaker - 就是负责保存和恢复工作的角色,在本例中由Main函数扮演此角色。 - 需要注意的是:为了不破坏封装性,Memento暴露给Caretaker的内容很少,少到刚好够Caretaker使用。 - 比如本例中Main函数的判断条件需要money的值,Memento就只暴露一个getMoney方法,其他的一点不多给。 
思路拓展
接口可见性
本例的代码结构如图:

java的方法可见性规定
| 可见性 | 说明 | 
|---|---|
| public | 所有类都可以访问 | 
| protected | 同一个包内或该类的子类可以访问 | 
| 无 | 同一个包内的类可以访问 | 
| private | 只有该类才能访问 | 
Memento类中的可见性
| 可见性 | 成员 | 谁可以访问 | 
|---|---|---|
| 无 | money | Memento、Gamer | 
| 无 | fruits | Memento、Gamer | 
| public | getMoney | Memento、Gamer、Main | 
| 无 | Memento | Memento、Gamer | 
| 无 | addFruit | Memento、Gamer | 
| 无 | getFruits | Memento、Gamer | 
只有getMoney方法可以被包外的类访问,它是一个窄接口,“窄”指的是此接口能操作的类内部的内容很少。
通过对方法的访问权限的控制,可以提升封装性,减少内部结构对外暴露引发的一系列问题。
保存多少个Memento
比如word软件,支持多次撤销功能,就需要保存多个Memento。可以用集合来存储。
划分Caretaker和Originator的意义
还是职责分离
Originator负责保存和恢复的具体实现。
Caretaker负责执行保存和恢复动作。
如果需求变化了,要求
- 支持多次撤销
- 不仅可以多次撤销,还能保存到文件或数据库
此时就只需要修改Originator即可,不必修改Caretaker。
《图解设计模式》读书笔记8-2 MEMENTO模式的更多相关文章
- HeadFirst设计模式读书笔记(3)-装饰者模式(Decorator Pattern)
		装饰者模式:动态地将责任附件到对象上.若要扩展功能,装饰者提东了比继承更有弹性的替代方案. 装饰者和被装饰对象有相同的超类型 你可以用一个或者多个装饰者包装一个对象. 既然装饰者和被装饰对象有相同的超 ... 
- HeadFirst设计模式读书笔记--目录
		HeadFirst设计模式读书笔记(1)-策略模式(Strategy Pattern) HeadFirst设计模式读书笔记(2)-观察者模式(Observer Pattern) HeadFirst设计 ... 
- Head First 设计模式读书笔记(1)-策略模式
		一.策略模式的定义 策略模式定义了算法族,分别封装起来,让它们之间可以互换替换,此模式让算法的变化独立使用算法的客户. 二.使用策略模式的一个例子 2.1引出问题 某公司做了一套模拟鸭子的游戏:该游戏 ... 
- C#设计模式学习笔记:(22)备忘录模式
		本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8176974.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第十个模式--备 ... 
- JavaScript设计模式:读书笔记(未完)
		该篇随我读书的进度持续更新阅读书目:<JavaScript设计模式> 2016/3/30 2016/3/31 2016/4/8 2016/3/30: 模式是一种可复用的解决方案,可用于解决 ... 
- 图解http读书笔记
		以前对HTTP协议一知半解,一直不清楚前端需要对于HTTP了解到什么程度,知道接触的东西多了,对于性能优化.服务端的配合和学习中也渐渐了解到了HTTP基础的重要性,看了一些大神对HTTP书籍的推荐,也 ... 
- Java设计模式学习笔记(二) 简单工厂模式
		前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 正文开始... 1. 简介 简单工厂模式不属于GoF23中设计模式之一,但在软件开发中应用也较为 ... 
- Java设计模式学习笔记(三) 工厂方法模式
		前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 简介 上一篇博客介绍了简单工厂模式,简单工厂模式存在一个很严重的问题: 就是当系统需要引入 ... 
- Java设计模式学习笔记(四) 抽象工厂模式
		前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 抽象工厂模式概述 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问 ... 
- C#设计模式学习笔记:(23)解释器模式
		本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8242238.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第十一个模式-- ... 
随机推荐
- 执行npm publish 报错:401 Unauthorized - PUT https://registry.npmjs.org/kunmomotest - You must be logged in to publish packages.
			前言 执行npm publish 报错:401 Unauthorized - PUT https://registry.npmjs.org/kunmomotest - You must be logg ... 
- python学习笔记(2):科学计算及数据可视化入门
			一.NumPy 1.NumPy:Numberical Python 2.高性能科学计算和数据分析的基础包 3.ndarray,多维数组(矩阵),具有矢量运算的能力,快速.节省空间 (1)ndarray ... 
- author认证模块
			author认证模块 用auth模块 你就用全套 不是自己写一部分 用别人一部分  创建超级管理员,用于登录DJango admin的后台管理  命令:createsuperuser,输入顺序用户 ... 
- python基础知识的入门介绍
			一.什么是编程语言 任何词语都是一种高度的概括和总结,所以找关键字.如下: (1)1.什么是"语言":一个人与另一个人沟通的介质 2人将自己的思维逻辑和想法通过计算机能过识别的语言 ... 
- STM32程序加载与调试
			1.STM32程序的ISP下载,只能使用串口1,其它串口不可以. 
- tr 替换或删除字符
			1.命令功能 tr 从标准输入中替换,压缩间隔或者删除字符并从定向到标准输出. 2.语法格式 tr option SET1 SET2 参数 参数说明 -c 取代所有SET1中字符串 -d 删除所 ... 
- JavaScript设计模式样例四 —— 单例模式
			单例模式(Singleton Pattern): 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 目的:阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例. 场景: ... 
- [人工智能]IBM Watson人工智能API|一步步创建智能微信翻译官|第一章
			最近参加了IBM可认知内部创业活动,想从零创建一个微信翻译工具,这就是我的AI翻译官. 
- layui  动态添加 表格数据
			静态表格: <table class="layui-table" id="table" lay-filter="table"> ... 
- NTC电阻Rt与温度T关系
			NTC电阻Rt与温度T公式如下: Rt=10000*exp(3950*(1/(273.15+T)-1/(273.15+25))). 例:0摄氏度时,电阻为33620.6037214357 欧姆 Rt= ... 
