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

对备忘录模式的理解

对备忘录模式的理解

    • 一、场景
      • 1、题目【[来源](https://kamacoder.com/problempage.php?pid=1095)】
        • 1.1 题目描述
        • 1.2 输入描述
        • 1.3 输出描述
        • 1.4 输入示例
        • 1.5 输出示例
      • 2、理解需求
    • 二、不采用备忘录设计模式
      • 1、代码
      • 2、问题
      • 3、错误的备忘录模式
    • 三、采用备忘录设计模式
      • 1、代码
        • 1.1 Originator(原发器)
        • 1.2 Memento(备忘录)
        • 1.3 Caretaker(负责人)
        • 1.4 客户端
      • 2、思考

一、场景

1、题目【来源】

1.1 题目描述

小明正在设计一个简单的计数器应用,支持增加(Increment)和减少(Decrement)操作,以及撤销(Undo)和重做(Redo)操作,请你使用备忘录模式帮他实现。

1.2 输入描述

输入包含若干行,每行包含一个字符串,表示计数器应用的操作,操作包括 “Increment”、“Decrement”、“Undo” 和 “Redo”。

1.3 输出描述

对于每个 “Increment” 和 “Decrement” 操作,输出当前计数器的值,计数器数值从0开始 对于每个 “Undo” 操作,输出撤销后的计数器值。 对于每个 “Redo” 操作,输出重做后的计数器值。

1.4 输入示例
Increment
Increment
Decrement
Undo
Redo
Increment
1.5 输出示例
1
2
1
2
1
2

2、理解需求

  • 增加(Increment)和减少(Decrement)操作比较好理解,不赘述了。

  • 重点理解下:撤销(Undo)和重做(Redo)操作。

    image

    • 一般编辑器,都支持Undo和Redo操作。

    • Undo操作:因为操作导致值发生变化,例如,0变成1。我们需要记下变化的值,这样才方便用户回退。

      • 很明显,应该用栈来记录。例如:0 -> 1 -> 2 -> 3。 当前处于3,接下来Undo,应该从3变成2。也就是把3从栈中弹出。
    • Redo操作:依然基于“0 -> 1 -> 2 -> 3”进行说明,当前处于3,用户Undo后,3变成2,接下来,用户Redo了,也就是希望2又变回3。

      • 也就是,我们需要记录Undo栈中弹出来的值。很显然,也是一个栈。

二、不采用备忘录设计模式

1、代码

public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int res = 0;// 栈Deque<Integer> undoStack = new ArrayDeque<>();undoStack.push(res);Deque<Integer> redoStack = new ArrayDeque<>();while (scanner.hasNextLine()) {String command = scanner.nextLine();res = runCommand(command, res, undoStack, redoStack);System.out.println(res);}}private static Integer runCommand(String command, Integer res, Deque<Integer> undoStack, Deque<Integer> redoStack) {if ("Increment".equals(command)) {res += 1;undoStack.push(res);return res;} else if ("Decrement".equals(command)) {res -= 1;undoStack.push(res);return res;} else if ("Undo".equals(command)) {if (undoStack.size() == 1) {// 相当于还没有做任何操作,用户就执行了Undoreturn undoStack.peek();} else if (undoStack.size() > 1) {Integer value = undoStack.pop();redoStack.push(value);return undoStack.peek();}} else if ("Redo".equals(command)) {if (!redoStack.isEmpty()) {Integer value = redoStack.pop();undoStack.push(value);return value;}}return res;}
}

2、问题

  • Increment等操作是客户端(main方法)的命令,客户端不应该看到undoStack、redoStack等数据。

    • 上面的写法是典型的面向过程开发,我们需要使用面向对象开发。

      image

  • 很显然,我们需要设计一个Calculator。

    • public class Calculator {private int value;public Calculator() {this.value = 0;}public Integer runCommand(String command) {return null;}
      }public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);Calculator calculator = new Calculator();while (scanner.hasNextLine()) {String command = scanner.nextLine();Integer res = calculator.runCommand(command);System.out.println(res);}}
      }
      
  • 为了实现Undo、Redo,这个类的对象有一个特点,需要保存和恢复对象之前的状态。

    • 备忘录模式是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。[先有场景,后有设计模式]

3、错误的备忘录模式

public class Calculator {private int value;private Deque<Integer> undoStack;private Deque<Integer> redoStack;public Calculator() {this.value = 0;this.undoStack = new ArrayDeque<>();undoStack.push(value);this.redoStack = new ArrayDeque<>();}public Integer runCommand(String command) {if ("Increment".equals(command)) {value += 1;undoStack.push(value);return value;} else if ("Decrement".equals(command)) {value -= 1;undoStack.push(value);return value;} else if ("Undo".equals(command)) {if (undoStack.size() == 1) {// 相当于还没有做任何操作,用户就执行了Undoreturn undoStack.peek();} else if (undoStack.size() > 1) {Integer v = undoStack.pop();redoStack.push(v);return undoStack.peek();}} else if ("Redo".equals(command)) {if (!redoStack.isEmpty()) {Integer v = redoStack.pop();undoStack.push(v);return v;}}return value;}
}
  • Calculator这个类是违背单一职责的,按照备忘录模式的经典设计,应该具有3个角色:

    • Originator(原发器):状态持有者,并且可以请求保存状态和恢复状态。
    • Memento(备忘录):负责保存状态和恢复状态。
    • Caretaker(负责人):负责管理备忘录。

三、采用备忘录设计模式

1、代码

1.1 Originator(原发器)
public class Counter {private int value;public Counter() {this.value = 0;}public int getValue() {return value;}public void increment() {this.value++;}public void decrement() {this.value--;}public Memento createMemento() {return new Memento(this.value);}public void restoreMemento(Memento memento) {this.value = memento.getValue();}
}
1.2 Memento(备忘录)
public class Memento {private int value;public Memento(int value) {this.value = value;}public int getValue() {return value;}
}
1.3 Caretaker(负责人)
public class Calculator {private Counter counter;private Deque<Memento> undoStack;private Deque<Memento> redoStack;public Calculator() {counter = new Counter();undoStack = new ArrayDeque<>();redoStack = new ArrayDeque<>();}public Integer runCommand(String command) {if (command.equals("Increment")) {counter.increment();undoStack.push(counter.createMemento());redoStack.clear(); // redoStack是专门用来记录undoStack弹出的状态的,undoStack放入新状态后,redoStack里面的状态就无效了} else if (command.equals("Decrement")) {counter.decrement();undoStack.push(counter.createMemento());redoStack.clear();} else if (command.equals("Undo")) {if (!undoStack.isEmpty()) {Memento memento = undoStack.pop();redoStack.push(memento);counter.restoreMemento(undoStack.peek());}} else if (command.equals("Redo")) {if (!redoStack.isEmpty()) {Memento memento = redoStack.pop();counter.restoreMemento(memento);undoStack.push(memento);}} else {throw new RuntimeException("Unknown command");}return counter.getValue();}
}
1.4 客户端
public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);Calculator calculator = new Calculator();while (scanner.hasNextLine()) {String command = scanner.nextLine();Integer res = calculator.runCommand(command);System.out.println(res);}}
}

2、思考

  • 相比“3、错误的备忘录模式”,每个类的职责更单一一些。但,Memento好麻烦啊。相当于把Counter的字段复制了一遍。以后Counter加一个字段,Memento就要补一个字段。太麻烦了。

  • 一种不错的解决办法是:序列化。

  • public class Counter {private int value;public Counter() {}public void setValue(int value) {this.value = value;}public int getValue() {return value;}public void increment() {this.value++;}public void decrement() {this.value--;}public void restoreMemento(Memento memento) {String backup = memento.getBackup();Counter tmpCounter = JSON.parseObject(backup, Counter.class);this.value = tmpCounter.value;}public Memento createMemento() {String backup = JSON.toJSONString(this);return new Memento(backup);}
    }public class Memento {private String backup;public Memento(String backup) {this.backup = backup;}public String getBackup() {return this.backup;}
    }
    

相关文章:

对备忘录模式的理解

对备忘录模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1095)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 2、理解需求 二、不采用备忘录设计模式1、代码2、问题3、错误的备忘录模式 三、采用备忘录设计模式1、代码1.1 …...

【国产突围!致远电子ZXDoc如何打破Vector垄断,成为新能源车研发“神器”?】

摘要&#xff1a;在汽车“新四化”浪潮下&#xff0c;国产汽车总线工具链软件正迎来高光时刻&#xff01;广州致远电子推出的ZXDoc以全栈自主化技术硬核国产芯片生态&#xff0c;斩获2024金辑奖“最佳技术实践应用奖”&#xff0c;成为新能源车企研发工程师的“效率倍增器”。本…...

【数据结构】图的基本概念

图的定义 通俗来说一堆顶点被一堆线连在一起&#xff0c;这一坨顶点与线的集合 目录 图的定义 术语 有向图与无向图 简单图与多重图 度、入度与出度 路径与回路 路径长度与距离 子图 连通、连通图与连通分量 强连通、强连通图与强连通分量 完全图 生成树与生成森林 权…...

常见的HR面问题汇总

⚠️注意&#xff1a;以下仅是个人对问题的参考&#xff0c;具体情况视个人情况而定&#xff5e; 1. 你觉得你有哪些优点和缺点&#xff1f; 优点&#xff1a;学习能力强&#xff0c;遇到问题会主动思考和查找解决方案&#xff1b;有责任心&#xff0c;对待工作认真负责&#…...

【微知】ARM CPU是如何获取某个进程的页表的?(通过TTBR寄存器,MMU进行处理)

ARM CPU 中用于存储访问某个进程的页表的寄存器是 TTBR&#xff08;Translation Table Base Register&#xff09;。有TTBR0和TTBR1。TTBR0用户空间的一级页表基址&#xff0c;1是内核页表。cpu访存获取物理地址流程 如果mmu发现tlb里面miss就通过pdbg拿pa物理地址。Intel是CR3…...

从零开始:在Qt中使用OpenGL绘制指南

从零开始&#xff1a;在Qt中使用OpenGL绘制指南 本文只介绍基本的 QOpenGLWidget 和 QOpenGLFunctions 的使用&#xff0c;想要学习 OpenGL 的朋友&#xff0c;建议访问经典 OpenGL 学习网站&#xff1a;LearnOpenGL CN 本篇文章&#xff0c;我们将以绘制一个经典的三角形为例&…...

激光加工中平面倾斜度的矫正

在激光加工中&#xff0c;加工平面的倾斜度矫正至关重要&#xff0c;直接影响加工精度和材料处理效果。以下是系统的矫正方法和步骤&#xff1a; 5. 验证与迭代 二次测量&#xff1a;加工后重新检测平面度&#xff0c;确认残余误差。 反馈优化&#xff1a;根据误差分布修正补偿…...

Android学习总结之应用启动流程(从点击图标到界面显示)

一、用户交互触发&#xff1a;Launcher 到 AMS 的跨进程通信 1. Launcher 处理点击事件&#xff08;应用层&#xff09; 当用户点击手机桌面上的应用图标时&#xff0c;Launcher&#xff08;桌面应用&#xff09;首先捕获点击事件。每个图标对应一个启动 Intent&#xff08;通…...

rdiff-backup备份

目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…...

PE结构(十五)系统调用与函数地址动态寻找

双机调试 当需要分析一个程序时&#xff0c;这个程序一定是可以调试的&#xff0c;操作系统也不例外。在调试过程中下断点是很重要的 当我们对一个应用程序下断点时&#xff0c;应用程序是挂起的。但当我们对操作系统的内核程序下断点时&#xff0c;被挂起的不是内核程序而是…...

webrtc 本地运行的详细操作步骤 1

前言 选修课的一个课程设计&#xff0c;需要我们本地运行这个开源项目&#xff0c;给我的压力非常大&#xff0c;因为确实不是很熟练这种操作。但是还是得做。谨以此文&#xff0c;纪念这个过程。 之前自己在 github 上面看到有代码仓库&#xff0c;但是比较复杂&#xff0c;在…...

kali——httrack

目录 前言 使用教程 前言 HTTrack 是一款运行于 Kali Linux 系统中的开源网站镜像工具&#xff0c;它能将网站的页面、图片、链接等资源完整地下载到本地&#xff0c;构建出一个和原网站结构相似的离线副本。 使用教程 apt install httrack //安装httrack工具 httrac…...

力扣经典算法篇-6-轮转数组

题干&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步…...

【计算机网络】Linux配置SNAT/DNAT策略

什么是NAT&#xff1f; NAT 全称是 Network Address Translation&#xff08;网络地址转换&#xff09;&#xff0c;是一个用来在多个设备共享一个公网 IP上网的技术。 NAT 的核心作用&#xff1a;将一个网络中的私有 IP 地址&#xff0c;转换为公网 IP 地址&#xff0c;从而…...

火山引擎coze用户市场

火山引擎 **Coze**&#xff08;扣子&#xff09;的用户市场主要集中在 **需要快速构建和部署智能对话应用的企业及开发者群体**&#xff0c;覆盖多个行业与场景。以下是具体分析&#xff1a; --- ### **一、核心用户群体** 1. **企业用户** - **互联网/科技公司**&#…...

qt designer 软件主题程序设计

对于使用Qt Designer设计的界面&#xff0c;主题切换的实现需要结合Qt的信号槽机制、样式表动态加载以及资源管理。以下是针对Qt Designer UI的详细解决方案&#xff1a; 一、UI文件与主题系统的整合架构 二、核心实现步骤 1. 动态样式表加载系统 // ThemeManager.h class …...

2025/4/2 心得

第一题 题目描述 给定1001个范围在[1,1000]的数字&#xff0c;保证只有1个数字重复出现2次&#xff0c;其余数字只出现1次。试用O(n)时间复杂度来求出出现2次的这个数字。 不允许用数组 输入格式 第一行&#xff1a;一个整数1001&#xff1b; 第二行&#xff1a;1001个用…...

AI安全:构建负责任且可靠的系统

AI已成为日常生活中无处不在的助力&#xff0c;随着AI系统能力和普及性的扩展&#xff0c;安全因素变得愈发重要。从基础模型构建者到采用AI解决方案的企业&#xff0c;整个AI生命周期中的所有相关方都必须共同承担责任。 为什么AI安全至关重要&#xff1f; 对于企业而言&…...

VUE+SPRINGBOOT+语音技术实现智能语音歌曲管理系统

语音控制歌曲的播放、暂停、增删改查 <template><div class"Music-container"><div style"margin: 10px 0"><!--检索部分--><el-input style"width: 200px;" placeholder"请输入歌曲名称"v-model"sen…...

使用 SignalR 在 .NET Core 8 最小 API 中构建实时通知

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/90448094 介绍 构建实时应用程序已成为现代 Web 开发中必不可少的部分&#xff0c;尤其是对于通知、聊天系统和实时更新等功能。SignalR 是 ASP.NET 的一个强大库&#xff0c;可实现服务器端代码和客户…...

Kotlin 集合函数:map 和 first 的使用场景

Kotlin 提供了丰富的集合操作函数&#xff0c;使开发者可以更加简洁、高效地处理数据。其中&#xff0c;map 和 first 是两个常用的函数&#xff0c;分别用于转换集合和获取集合中的第一个元素。 1. map 的使用场景 场景 1&#xff1a;对象列表转换 在开发中&#xff0c;我们…...

Spring Cloud 框架为什么能处理高并发

Spring Cloud框架能够有效处理高并发场景&#xff0c;核心在于其微服务架构设计及多组件的协同作用&#xff0c;具体机制如下&#xff1a; 一、分布式架构设计支撑高扩展性 服务拆分与集群部署 Spring Cloud通过微服务拆分将单体系统解耦为独立子服务&#xff0c;每个服务可独…...

【Python爬虫高级技巧】BeautifulSoup高级教程:数据抓取、性能调优、反爬策略,全方位提升爬虫技能!

大家好&#xff0c;我是唐叔&#xff01;上期我们聊了 BeautifulSoup的基础用法 &#xff0c;今天带来进阶篇。我将分享爬虫老司机总结的BeautifulSoup高阶技巧&#xff0c;以及那些官方文档里不会告诉你的实战经验&#xff01; 文章目录 一、BeautifulSoup性能优化技巧1. 解析…...

复古未来主义屏幕辉光像素化显示器反乌托邦效果PS(PSD)设计模板样机 Analog Retro-Futuristic Monitor Effect

这款模拟复古未来主义显示器效果直接取材于 90 年代赛博朋克电影中的黑客巢穴&#xff0c;将粗糙的屏幕辉光和像素化的魅力强势回归。它精准地模仿了老式阴极射线管显示器&#xff0c;能将任何图像变成故障频出的监控画面或高风险的指挥中心用户界面。和……在一起 2 个完全可编…...

Spring Boot + MySQL + MyBatis(注解和XML配置两种方式)集成Redis的完整启用及配置详解,包含代码示例、注释说明和表格总结

以下是 Spring Boot MySQL MyBatis&#xff08;注解和XML配置两种方式&#xff09;集成Redis的完整启用及配置详解&#xff0c;包含代码示例、注释说明和表格总结&#xff1a; 1. 添加依赖 在pom.xml中添加Spring Boot对MySQL、MyBatis和Redis的支持依赖&#xff1a; <d…...

Webpack vs Vite:现代前端构建工具的巅峰对决与选型指南

构建工具的进化革命当雪碧瓶上的水珠折射出前端工程的变迁史&#xff0c;Webpack与Vite的决战已然成为现代前端开发的分水岭。这场始于打包理念的革命&#xff0c;正在重塑整个前端生态的底层逻辑。本文将从原理架构、性能表现、开发体验三个维度&#xff0c;结合真实项目数据对…...

2023-2024总结记录

概括经历 这一年算是一个人生节点&#xff0c;2023年花了一整年的时间在准备考研&#xff0c;基本上等于一个人奋战&#xff0c;我不怎么去图书馆&#xff0c;只呆在无人的实验室&#xff0c;还好有对象陪我&#xff0c;不然可能要抑郁了。作息上还是很随意&#xff0c;什么时…...

技术驱动革新,强力巨彩LED软模组助力创意显示

随着LED显示技术的不断突破&#xff0c;LED软模组因其独特的柔性特质和个性化显示效果&#xff0c;正逐渐成为各类应用场景的新宠。强力巨彩软模组R3.0H系列具备独特的可塑造型能力与技术创新&#xff0c;为商业展示、数字艺术、建筑装饰等领域开辟全新视觉表达空间。    LED…...

Spring 核心技术解析【纯干货版】- XVIII:Spring 网络模块 Spring-WebSocket 模块精讲

在现代 Web 开发中&#xff0c;实时通信已成为提升用户体验的关键技术之一。传统的 HTTP 轮询方式存在较高的延迟和带宽开销&#xff0c;而 WebSocket 作为一种全双工通信协议&#xff0c;能够在客户端和服务器之间建立持久连接&#xff0c;实现高效的双向数据传输。 Spring 框…...

Spark,HDFS概述

HDFS组成构架&#xff1a; 注&#xff1a; NameNode&#xff08;nn&#xff09;&#xff1a;就是 Master&#xff0c;它是一个主管、管理者。 (1) 管理 HDFS 的名称空间&#xff1b; (2) 配置副本策略。记录某些文件应该保持几个副本&#xff1b; (3) 管理数据块&#xff08;…...