聊聊设计模式-备忘录模式?

简介

备忘录模式是一种行为设计模式,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态,也就是在不破坏封装性的前提下,捕获一下对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

有点懵,如何在不暴露对象细节的情况下保存和恢复之前的状态(也可以理解为回滚或者撤销功能)?

我们知道,我们想要生成某个对象的快照,或者说副本,很可能需要遍历对象的所有成员变量并将其此刻的数值赋值保存。但是这种情况只有当对象对其内容没有严格的访问权限控制的情况下才可使用。

但是绝大部分对象都会被声明为private私有对象来存储重要数据,以保证数据的安全性。如果我们要让其它对象能够保存和读取该快照,很可能需要将快照的成员变量设为共有。

解决方案

我们刚才遇到的所有问题都是封装”破损“造成的。一些对象试图超出其职责范围的工作。由于在执行某些行为时需要获取数据,所以它们侵入了其它对象的私有空间,而不是让这些对象来完成实际的工作。

备忘录结构

备忘录模式将创建状态快照(Snapshot)的工作委派给实际状态的拥有者原发器(Originator)对象。这样其它对象就不再需要从”外部“复制编辑器状态了,编辑器类拥有其状态的完全访问权,因此可以自行生成快照。

模式建议将对象状态的副本存储在一个名为备忘录(Memento)的特殊对象中。除了创建备忘录的对象外,任何对象都不能访问备忘录的内容。其他对象必须使用受限接口与备忘录进行交互,它们可以获取快照的元数据(创建时间和操作名称等),但不能获取快照中原始对象的状态。

简单解释就是,我们只需要让对象本身,自己生成备忘录(构造、克隆、序列化等方式),而生成的这个快照就是备忘录 Memento,它只能够被创建对象Originator所访问。然后我们再通过一个限制了访问权限的接口去提供获取备忘录Memento

实现方式

  • 基于嵌套类实现
  • 基于中间接口实现
基于嵌套类实现
  • 原发器:它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
  • 备忘录:是原发器状态快照的值对象(value object)。通常做法是将备忘录设为不可变的,并通过构造函数一次性传递数据,存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
  • 负责人:又称为管理者,它负责保存备忘录,仅知道”何时“和”为何“捕捉原发器的状态,以及何时恢复状态,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

负责人通过保存备忘录栈或者集合来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人将从栈中获取最顶部的备忘录,并将其传递给原发器的恢复(restoration)方法(集合则通过index下标变换)

访问权限

在该实现方法中,备忘录类将被嵌套在原发器中。这样原发器就可访问备忘录的成员变量和方法,即使这些方法将声明为私有。另一方面,负责人对于备忘录的成员变量和方法的访问权限非常有限:它们只能在栈中保存备忘录,而不能修改其状态。

案例

假设我们现在要来设计象棋的悔棋功能,那么应该如何设计?

  • 我们来分析一下,我们要记录某个玩家历史时刻的下棋位置,需要记录棋子的坐标等信息。很明显,集成了这些信息的类就是Originator原发器类
  • 然后我们需要对原发器进行记录,也就是生成备忘录Memento
  • 再有一个负责人去存储备忘录,进行操作

代码

package com.备忘录模式;

import java.util.ArrayList;

/**
 * 象棋棋子类
 * @author Fang Ruichuan
 * @date 2022-12-20 20:59
 */
public class Chessman {
    private String label;
    private int x;
    private int y;

    public Chessman(String label, int x, int y) {
        this.label = label;
        this.x = x;
        this.y = y;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    // 保存状态
    public ChessmanMemento save() {
        return new ChessmanMemento(this.label, this.x, this.y);
    }

    // 恢复状态
    public void restore(ChessmanMemento memento) {
        this.label = memento.getLabel();
        this.x = memento.getX();
        this.y = memento.getY();
    }
}


// 象棋棋子备忘录类:备忘录
class ChessmanMemento {
    private String label;
    private int x;
    private int y;

    public ChessmanMemento(String label, int x, int y) {
        this.label = label;
        this.x = x;
        this.y = y;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

// 象棋棋子备忘录管理类:负责人
class MementoCareTaker {
    // 定义一个集合来存储多个备忘录
    private ArrayList<ChessmanMemento> mementoList = new ArrayList<ChessmanMemento>();

    public ChessmanMemento getMemento(int i) {
        return mementoList.get(i);
    }

    public void setMemento(ChessmanMemento memento) {
        mementoList.add(memento);
    }
}

// 客户端
class Client {
    private static int index = -1;  // 定义一个索引来记录当前状态所在位置
    private static MementoCareTaker mc = new MementoCareTaker();

    public static void main(String[] args) {
        Chessman chess = new Chessman("车", 1, 1);
        play(chess);
        chess.setY(4);
        play(chess);
        chess.setX(5);
        play(chess);
        undo(chess, index);
        undo(chess, index);
        redo(chess, index);
        redo(chess, index);
    }

    // 下棋
    public static void play(Chessman chess) {
        mc.setMemento(chess.save());     // 保存备忘录
        index++;
        System.out.println("棋子: " + chess.getLabel() + ",当前位置为:" + "第" + chess.getX() + "行,第" + chess.getY() + "列。");
    }

    // 悔棋
    public static void undo(Chessman chess, int i) {
        System.out.println("********悔棋********");
        index--;
        chess.restore(mc.getMemento(i - 1));    // 撤销到上一个备忘录
        System.out.println("棋子 " + chess.getLabel() + ",当前位置为:" + "第" + chess.getX() + "行,第" + chess.getY() + "列。");
    }

    // 撤销悔棋
    public static void redo(Chessman chess, int i) {
        System.out.println("********撤销悔棋*******");
        index++;
        chess.restore(mc.getMemento(i + 1));    // 恢复到下一个备忘录
        System.out.println("棋子" + chess.getLabel() + "当前位置为:" + "第" + chess.getX() + "行,第" + chess.getY() + "列。");
    }
}

运行结果:

棋子: 车,当前位置为:第1行,第1列。
棋子: 车,当前位置为:第1行,第4列。
棋子: 车,当前位置为:第5行,第4列。
********悔棋********
棋子 车,当前位置为:第1行,第4列。
********悔棋********
棋子 车,当前位置为:第1行,第1列。
********撤销悔棋*******
棋子车当前位置为:第1行,第4列。
********撤销悔棋*******
棋子车当前位置为:第5行,第4列。

小结

对于各个角色

  • 对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;
  • 对于负责人而言,它负责备忘录的保存并将备忘录传递给其他对象;
  • 对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值