设计模式之【备忘录模式】,“后悔药”是可以有的

这篇具有很好参考价值的文章主要介绍了设计模式之【备忘录模式】,“后悔药”是可以有的。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


全网最全最细的【设计模式】总目录,收藏起来慢慢啃,看完不懂砍我

一、什么是备忘录模式

备忘录模式(Memento Pattern)又称为快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态,属于行为型模式。

在软件系统中,备忘录模式可以为我们提供一种“后悔药”的机制,它通过存储系统各个历史状态的快照,使得我们可以在任一时刻将系统回滚到某一个历史状态。

备忘录模式本质是从发起人实体类(Originator)隔离存储功能,降低实体类的职责。同时由于存储信息(Memento)独立,且存储信息的实体交由管理类(Caretaker)管理,则可以通过为管理类扩展额外的功能对存储信息进行扩展操作(比如增加历史快照功能)。

1、备忘录模式使用场景

  • 需要保存历史快照的场景。
  • 希望在对象之外保存状态,且除了自己其他类对象无法访问状态保存具体内容。

比如,玩游戏时的中间结果的存档功能、如 Word、记事本、Photoshop,idea等软件在编辑时按Ctrl+Z 组合键,还有数据库中事务操作。

2、备忘录模式优缺点

优点:

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

缺点:

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

3、备忘录模式的三大角色

设计模式之【备忘录模式】,“后悔药”是可以有的
备忘录模式的主要角色如下:

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

4、白箱备忘录和黑箱备忘录

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

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

白箱备忘录使用的就是宽接口,白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。

黑箱备忘录使用的是窄接口,将备忘录角色封装在发起人角色的内部形成一个私有的内部类,并实现窄接口。管理者只管理窄接口,这样可以屏蔽备忘录角色的细节。

5、思考:备份频率快,备份对象大的备忘录应该如何设计

假设每当有数据改动,我们都需要生成一个备份,以备之后恢复。如果需要备份的数据很大,这样高频率的备份,不管是对存储(内存或者硬盘)的消耗,还是对时间的消耗,都可能是无法接受的。想要解决这个问题,我们一般会采用“低频率全量备份”和“高频率增量备份”相结合的方法。

当我们需要恢复到某一时间点的备份的时候,如果这一时间点有做全量备份,我们直接拿来恢复就可以了。如果这一时间点没有对应的全量备份,我们就先找到最近的一次全量备份,然后用它来恢复,之后执行此次全量备份跟这一时间点之间的所有增量备份,也就是对应的操作或者数据变动。这样就能减少全量备份的数量和频率,减少对时间、内存的消耗。

其实很多设计原则和设计思想都是互通的,mysql的备份与恢复、redis的备份与恢复都是参考了这种实现原理。

二、实例

1、备忘录模式的一般写法

// 发起人角色
public class Originator {
    // 内部状态
    private String state;

    public String getState() {
        return this.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());
    }
}
// 备忘录角色
public class Memento {
    private String state;

    public Memento(String state){
        this.state = state;
    }

    public String getState() {
        return this.state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
// 管理者角色
public class Caretaker {
    // 备忘录对象
    private Memento memento;

    public Memento getMemento() {
        return this.memento;
    }

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

}
// 测试类
public class Test {
    public static void main(String[] args) {
        //来一个发起人
        Originator originator = new Originator();
        //来一个备忘录管理员
        Caretaker caretaker = new Caretaker();
        //管理员存储发起人的备忘录
        caretaker.storeMemento(originator.createMemento());
        //发起人从管理员获取备忘录进行回滚
        originator.restoreMemento(caretaker.getMemento());

    }
}

2、使用栈管理富文本编辑器

我们使用富文本编辑器时,会经常写入、撤销、修改。因此我们需要将每一时刻的修改记录都要保存在草稿箱中。

// 发起人角色编辑器
public class Editor {

    private String title;
    private String content;
    private String imgs;

    public Editor(String title, String content, String imgs) {
        this.title = title;
        this.content = content;
        this.imgs = imgs;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    public String getImgs() {
        return imgs;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setImgs(String imgs) {
        this.imgs = imgs;
    }

    public ArticleMemento saveToMemento(){
        ArticleMemento articleMemento = new ArticleMemento(this.title,this.content,this.imgs);
        return articleMemento;
    }

    public void undoFromMemento(ArticleMemento articleMemento){
        this.title = articleMemento.getTitle();
        this.content = articleMemento.getContent();
        this.imgs = articleMemento.getImgs();
    }

    @Override
    public String toString() {
        return "Editor{" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", imgs='" + imgs + '\'' +
                '}';
    }
}

// 备忘录角色
public class ArticleMemento {
    private String title;
    private String content;
    private String imgs;

    public ArticleMemento(String title, String content, String imgs) {
        this.title = title;
        this.content = content;
        this.imgs = imgs;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    public String getImgs() {
        return imgs;
    }

    @Override
    public String toString() {
        return "ArticleMemento{" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", imgs='" + imgs + '\'' +
                '}';
    }
}

// 管理角色 草稿箱
public class DraftsBox {
    private final Stack<ArticleMemento> STACK = new Stack<ArticleMemento>();

    public ArticleMemento getMemento(){
        ArticleMemento articleMemento = STACK.pop();
        return articleMemento;
    }

    public void addMemento(ArticleMemento articleMemento){
        STACK.push(articleMemento);
    }

}

草稿箱中定义的Stack类是Vector的一个子类,它实现了一个标准的后进先出的栈。主要定义了以下方法:

方法定义 方法描述
boolean empty() 测试栈是否为空
Object peek() 查看栈顶对象,但不从栈中移除它
Object pop() 移除栈顶对象,并作为此函数的返回值
Object push(Object element) 把对象压入栈顶
int search(Object element) 返回对象在栈中的位置,以1为基数
// 测试类
public class Test {
    public static void main(String[] args) {
        DraftsBox draftsBox = new DraftsBox();

        Editor editor = new Editor("标题1",
                "内容1",
                "图片1");

        ArticleMemento articleMemento = editor.saveToMemento();
        draftsBox.addMemento(articleMemento);

        System.out.println("标题:" + editor.getTitle() + "\n" +
                            "内容:" + editor.getContent() + "\n" +
                            "插图:" + editor.getImgs() + "\n暂存成功");

        System.out.println("完整的信息" + editor);


        System.out.println("==========首次修改文章===========");
        editor.setTitle("标题2");
        editor.setContent("内容2");
        editor.setImgs("图片2");

        System.out.println("==========首次修改文章完成===========");

        System.out.println("完整的信息" + editor);

        articleMemento = editor.saveToMemento();

        draftsBox.addMemento(articleMemento);

        System.out.println("==========保存到草稿箱===========");


        System.out.println("==========第2次修改文章===========");
        editor.setTitle("标题3");
        editor.setContent("内容3");
        editor.setImgs("图片3");
        System.out.println("完整的信息" + editor);
        System.out.println("==========第2次修改文章完成===========");

        System.out.println("==========第1次撤销===========");
        articleMemento = draftsBox.getMemento();
        editor.undoFromMemento(articleMemento);
        System.out.println("完整的信息" + editor);
        System.out.println("==========第1次撤销完成===========");


        System.out.println("==========第2次撤销===========");
        articleMemento = draftsBox.getMemento();
        editor.undoFromMemento(articleMemento);
        System.out.println("完整的信息" + editor);
        System.out.println("==========第2次撤销完成===========");

    }
}

执行结果:
设计模式之【备忘录模式】,“后悔药”是可以有的

3、游戏状态恢复案例

游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

(1)“白箱”备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
设计模式之【备忘录模式】,“后悔药”是可以有的

//游戏角色类
public class GameRole {
	private int vit; //生命力
	private int atk; //攻击力
	private int def; //防御力
	//初始化状态
	public void initState() {
		this.vit = 100;
		this.atk = 100;
		this.def = 100;
	}
	//战斗
	public void fight() {
		this.vit = 0;
		this.atk = 0;
		this.def = 0;
	}
	//保存角色状态
	public RoleStateMemento saveState() {
		return new RoleStateMemento(vit, atk, def);
	}
	//回复角色状态
	public void recoverState(RoleStateMemento roleStateMemento) {
		this.vit = roleStateMemento.getVit();
		this.atk = roleStateMemento.getAtk();
		this.def = roleStateMemento.getDef();
	}
	public void stateDisplay() {
		System.out.println("角色生命力:" + vit);
		System.out.println("角色攻击力:" + atk);
		System.out.println("角色防御力:" + 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 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;
	}
}
//角色状态管理者类
public class RoleStateCaretaker {
	private RoleStateMemento roleStateMemento;
	public RoleStateMemento getRoleStateMemento() {
		return roleStateMemento;
	}
	public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
		this.roleStateMemento = roleStateMemento;
	}
}
//测试类
public class Client {
	public static void main(String[] args) {
		System.out.println("------------大战Boss前------------");
		//大战Boss前
		GameRole gameRole = new GameRole();
		gameRole.initState();
		gameRole.stateDisplay();
		//保存进度
		RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
		roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
		System.out.println("------------大战Boss后------------");
		//大战Boss时,损耗严重
		gameRole.fight();
		gameRole.stateDisplay();
		System.out.println("------------恢复之前状态------------");
		//恢复之前状态
		gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
		gameRole.stateDisplay();
	}
}

分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意

(2)“黑箱”备忘录模式

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

将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在GameRole 里面;在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。
这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而 RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。类图如下:
设计模式之【备忘录模式】,“后悔药”是可以有的

// 窄接口 Memento ,这是一个标识接口,因此没有定义出任何的方法
public interface Memento {
}
// 定义发起人类 GameRole ,并在内部定义备忘录内部类 RoleStateMemento (该内部类设置为私有的)
//游戏角色类
public class GameRole {
	private int vit; //生命力
	private int atk; //攻击力
	private int def; //防御力
	//初始化状态
	public void initState() {
		this.vit = 100;
		this.atk = 100;
		this.def = 100;
	}
	//战斗
	public void fight() {
		this.vit = 0;
		this.atk = 0;
		this.def = 0;
	}
	//保存角色状态
	public Memento saveState() {
		return new RoleStateMemento(vit, atk, def);
	}
	//回复角色状态
	public void recoverState(Memento memento) {
		RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
		this.vit = roleStateMemento.getVit();
		this.atk = roleStateMemento.getAtk();
		this.def = roleStateMemento.getDef();
	}
	public void stateDisplay() {
		System.out.println("角色生命力:" + vit);
		System.out.println("角色攻击力:" + atk);
		System.out.println("角色防御力:" + 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;
	}
	// 备忘录角色内部类
	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;
		}
	}
}

负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容文章来源地址https://www.toymoban.com/news/detail-455361.html

//角色状态管理者类
public class RoleStateCaretaker {
	private Memento memento;
	public Memento getMemento() {
		return memento;
	}
	public void setMemento(Memento memento) {
		this.memento = memento;
	}
}
// 测试类
public class Client {
	public static void main(String[] args) {
		System.out.println("------------大战Boss前------------");
		//大战Boss前
		GameRole gameRole = new GameRole();
		gameRole.initState();
		gameRole.stateDisplay();
		//保存进度
		RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
		roleStateCaretaker.setMemento(gameRole.saveState());
		System.out.println("------------大战Boss后------------");
		//大战Boss时,损耗严重
		gameRole.fight();
		gameRole.stateDisplay();
		System.out.println("------------恢复之前状态------------");
		//恢复之前状态
		gameRole.recoverState(roleStateCaretaker.getMemento());
		gameRole.stateDisplay();
	}
}

到了这里,关于设计模式之【备忘录模式】,“后悔药”是可以有的的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设计模式行为型——备忘录模式

    目录 备忘录模式的定义 备忘录模式的实现 备忘录模式角色 备忘录模式类图 备忘录模式举例 备忘录模式代码实现 备忘录模式的特点 优点 缺点 使用场景 注意事项 实际应用         备忘录模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Token模式(Token Pattern),属

    2024年02月14日
    浏览(58)
  • Java设计模式-备忘录模式

    一、概述 备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z

    2024年01月21日
    浏览(51)
  • 【前端设计模式】之备忘录模式

    设计模式是在软件开发中经过验证的解决问题的方法。它们是从经验中总结出来的,可以帮助我们更好地组织和管理代码,提高代码的可维护性、可扩展性和可重用性。无论是前端还是后端开发,设计模式都扮演着重要的角色。在本专栏中,我们将探索一些常见的前端设计模

    2024年02月05日
    浏览(43)
  • 设计模式(十八)备忘录

    在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样就可以在以后将对象恢复到原先保存的状态。备忘录模式是一种对象行为型模式,其别名为标记(Token)模式。 备忘录模式的核心在于备忘录类以及用于管理备忘录的负责人类的设计,包

    2024年02月04日
    浏览(45)
  • 03-JAVA设计模式-备忘录模式

    Java中的备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以将对象恢复到原先保存的状态。 主要角色包括: 发起者(Originator):需要保存和恢复状态的对象。它记录当前

    2024年04月26日
    浏览(44)
  • 《设计模式的艺术》笔记 - 备忘录模式

            备忘录模式在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为模式,别名为Token。 myclass.h myclass.cpp main.cpp         1. 它提供了一种状态恢复的实现机制,使得用户可

    2024年01月24日
    浏览(64)
  • C++ 设计模式之备忘录模式

    【声明】本题目来源于卡码网(题目页面 (kamacoder.com)) 【提示:如果不想看文字介绍,可以直接跳转到C++编码部分】         -- 什么是备忘录模式  (第17种模式)          备忘录模式 (Memento Pattern)是⼀种 ⾏为型设计模式 ,它允许在不暴露对象实现的情况下捕获对

    2024年01月20日
    浏览(50)
  • 设计模式——备忘录模式(Memento Pattern)

    类型: 行为型模式 目的: 保存一个对象的某个状态,以便在适当的时候恢复对象。 使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。 2.1.1 定义副本类 2.1.2 定义对象 菜鸟的例子定义Originator 对象的同时,还提供saveStateToMemento、getStateFromMemento的

    2024年02月06日
    浏览(47)
  • Java设计模式之备忘录模式详解

    大家好,我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天,让我们一起探讨Java设计模式之备忘录模式,这种像时间旅行般的对象记忆术,是如何在程序的世界里实现的。 备忘录模式的引入 备忘录模式是一种

    2024年01月22日
    浏览(72)
  • 笨蛋学设计模式行为型模式-备忘录模式【22】

    8.9.1概念 ​ 备忘录模式允许在不暴露对象实现细节的情况下捕获和恢复对象的内部状态。通过将对象的状态封装在备忘录对象中,并将备忘录对象保存在一个管理者对象中,可以在需要时回滚到对象之前的状态。 8.9.2场景 ​ 在现代办公场景中,备忘录模式可以应用于文档编

    2024年01月25日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包