Java设计模式之备忘录模式

定义

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

结构

备忘录模式的主要角色如下:

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

备忘录有两个等效的接口:

  • 窄接口:管理者对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口,这个窄接口只允许他把备忘录对象传给其他的对象。
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

"白箱"备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。与上述窄接口冲突

发起人

java 复制代码
//游戏角色
public class GameRole {
    private int vit;//生命值
    private int atk;//攻击力
    private int def;//防御力

    public GameRole() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    public void To0(){
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    public int getVit() {
        return vit;
    }

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

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

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

    public RoleStateMemento saveRoleStateMemento(){
        return new RoleStateMemento(vit,atk,def);
    }

    public void recoverState(RoleStateMemento roleStateMemento){
        this.vit= roleStateMemento.getVit();
        this.atk= roleStateMemento.getAtk();
        this.def= roleStateMemento.getDef();
    }
}

备忘录管理类(提供保存与获取备忘录的功能)

java 复制代码
public class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;

    public RoleStateMemento getRoleStateMemento(){
        return roleStateMemento;
    }

    public void setRoleStateMemento(RoleStateMemento roleStateMemento){
        this.roleStateMemento = roleStateMemento;
    }
}

备忘录类(只做存储状态)

java 复制代码
public class RoleStateMemento {
    private int vit;//生命值
    private int atk;//攻击力
    private int def;//防御力

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

    public int getVit() {
        return vit;
    }

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

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

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

测试

java 复制代码
public class Client {
    public static void main(String[] args) {
        GameRole gameRole = new GameRole();
        //存档
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        RoleStateMemento roleStateMemento = gameRole.saveRoleStateMemento();
        roleStateCaretaker.setRoleStateMemento(roleStateMemento);

        System.out.println("初始状态:");
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());

        System.out.println("置零");
        gameRole.To0();
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());
        //读档
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        System.out.println("回档后状态");
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());
    }
}

初始状态:

100

100

100

置零

0

0

0

回档后状态

100

100

100

白箱备忘录模式破坏了封装性,在客户端部分可以通过备忘录类对象对发起人的属性进行更改。

"黑箱"备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类 设计成发起人类的内部成员类。

将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在 GameRole 里面;在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。(牛的)

备忘录窄接口

java 复制代码
给除了发起人的所有角色所示,只能拿到对应的对象,无法执行任何方法
public interface Memento {
}

备忘录管理类

java 复制代码
//(提供保存与获取备忘录的功能)
public class RoleStateCaretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
java 复制代码
//游戏角色
public class GameRole {
    private int vit;//生命值
    private int atk;//攻击力
    private int def;//防御力

    public GameRole() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    public void To0() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    public int getVit() {
        return vit;
    }

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

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

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

    //私有内部类,只供本类使用
    private class RoleStateMemento implements Memento {
        private int vit;//生命值
        private int atk;//攻击力
        private int def;//防御力

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

        public int getVit() {
            return vit;
        }

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

        public int getAtk() {
            return atk;
        }

        public void setAtk(int atk) {
            this.atk = atk;
        }

        public int getDef() {
            return def;
        }

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

    public Memento saveMemento() {
        return new RoleStateMemento(vit, atk, def);
    }

    public void recoverState(Memento memento) {
        //由于Memento类中什么都不存在,即无法设置发起人状态,因此需要进行一个强转对象
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }
}

测试

java 复制代码
public class Client {
    public static void main(String[] args) {
        GameRole gameRole = new GameRole();
        System.out.println("初始状态:");
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());

        //保存状态
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        Memento memento = gameRole.saveMemento();
        roleStateCaretaker.setMemento(memento);

        System.out.println("置零");
        gameRole.To0();
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());

        //回档
        gameRole.recoverState(memento);
        System.out.println("回档后状态");
        System.out.println(gameRole.getVit());
        System.out.println(gameRole.getAtk());
        System.out.println(gameRole.getDef());
    }
}

初始状态:

100

100

100

置零

0

0

0

回档后状态

100

100

100

优点

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

缺点

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

使用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
相关推荐
yanjiaweiya13 分钟前
云原生-集群管理
java·开发语言·云原生
gadiaola21 分钟前
【JavaSE面试篇】Java集合部分高频八股汇总
java·面试
艾迪的技术之路44 分钟前
redisson使用lock导致死锁问题
java·后端·面试
今天背单词了吗9801 小时前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师1 小时前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构1 小时前
订单初版—2.生单链路中的技术问题说明文档
java
Gavynlee1 小时前
plantuml用法总结
设计模式
咖啡啡不加糖2 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南2 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端