Windows环境下实现设计模式——备忘录模式(JAVA版)

news2024/10/2 12:23:41

我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现备忘录模式(设计模式)。

不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。

我今天就在Windows操作系统上安装好JAVA的IDE编程工具,并用JAVA语言来实现一个备忘录模式,真实的实现一个,你看懂代码后,自然就明白了。

备忘录模式Memento Pattern  (行为型设计模式)

定义:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。

 2015年那个会下围棋的人工智能很厉害吧。那这里我要问一下,那既然是人工智能,如果他下错棋了,怎么办?所以,在研制人工智能时,还得给人工智能增加一个梅棋的功能。

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,当前很多软件都提供了撤销(Undo)操作,比如说OFFICE,其中就使用了备忘录模式。

在实现撤销时,首先必须保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态。

 备忘录模式正为解决此类撤销问题而诞生,它为我们的软件提供了反悔的机制,通过使用备忘录模式可以使系统恢复到某一特定的历史状态。

 

在备忘录模式结构图中包含如下几个角色。 Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

理解备忘录模式关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。

在使用备忘录模式时,首先应该存在一个原发器类Originator,在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性,典型代码如下所示:

package designpatterns.memento;

public class Originator {
    private String state;
 
    public Originator(String state){
       this.state=state;
    }
 
  // 创建一个备忘录对象
    public Memento createMemento() {
    return new Memento(this);
    }
 
  // 根据备忘录对象恢复原发器状态
    public void restoreMemento(Memento m) {
     state = m.state;
    }
 
    public void setState(String state) {
        this.state=state;
    }
 
    public String getState() {
        return this.state;
    }
}

对于备忘录类Memento而言,它通常提供了与原发器相对应的属性用于存储原发器的状态,典型的备忘录类设计代码如下:

package designpatterns.memento;

//备忘录类,默认可见性,包内可见
class Memento {
    private String state;
 
    public Memento(Originator o) {
    state = o.getState();
    }
 
    public void setState(String state) {
        this.state=state;
    }
 
    public String getState() {
        return this.state;
    }
}

在设计备忘录类时需要考虑其封装性,除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法,如果不考虑封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态。

为了实现对备忘录对象的封装,需要对备忘录的调用进行控制,对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。

在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包中来实现封装,在Java语言中可使用默认访问标识符来定义Memento类,即保证其包内可见。只有Originator类可以对Memento进行访问,而限制了其他类对Memento的访问。在 Memento中保存了Originator的state值,如果Originator中的state值改变之后需撤销,可以通过调用它的restoreMemento()方法进行恢复。对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:

package designpatterns.memento;

public class Caretaker {
	private Memento memento;
 
	public Memento getMemento() {
		return memento;
	}
 
	public void setMemento(Memento memento) {
		this.memento=memento;
	}
}

 在Caretaker类中不应该直接调用Memento中的状态改变方法,它的作用仅仅用于存储备忘录对象。将原发器备份生成的备忘录对象存储在其中,当用户需要对原发器进行恢复时再将存储在其中的备忘录对象取出。

在客户端中通过创建Memento对象来保存原发器的历史状态,在需要的时候再用历史状态来覆盖当前状态,代码如下:

package designpatterns. memento;
 
public class Client {
  public static void main(String args[]){
  //创建原发器对象
  Originator ori= new Originator("状态1");
  System. out. println(ori. getState());

  //创建负责人对象,保存创建的备忘录对象
  Caretaker ct new Caretaker();
  ct. setMemento(ori. createMemento());

  ori. setState("状态2");
  System. out. println(ori. getState());

  /从负责人对象中取出备忘录对象,实现撤销
  ori. restoreMemento(ct. getMemento());
  System. out. println(ori. getState());
    }
}

输出结果为

状态1

状态2

状态1

为了实现撤销功能,我们使用备忘录模式来设计围棋软件,Goman充当原发器,GomanMemento充当备忘录,MementoCaretaker充当负责人,在MementoCaretaker中定义了一个GomanMemento类型的对象,用于存储备忘录。完整代码如下所示:

(1)Goman充当原发器

package designpatterns.memento;

public class Goman {
	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 void setLabel(String label) {
		this.label = label; 
	}
 
	public void setX(int x) {
		this.x = x; 
	}
 
	public void setY(int y) {
		this.y = y; 
	}
 
	public String getLabel() {
		return (this.label); 
	}
 
	public int getX() {
		return (this.x); 
	}
 
	public int getY() {
		return (this.y); 
	}
	
    //保存状态
	public GomanMemento save() {
		return new GomanMemento(this.label,this.x,this.y);
	}
	
    //恢复状态
	public void restore(GomanMemento memento) {
		this.label = memento.getLabel();
		this.x = memento.getX();
		this.y = memento.getY();
	}
}

(2)GomanMemento充当备忘录

package designpatterns.memento;

public class GomanMemento {
	private String label;
	private int x;
	private int y;
 
	public GomanMemento(String label,int x,int y) {
		this.label = label;
		this.x = x;
		this.y = y;
	}
 
	public void setLabel(String label) {
		this.label = label; 
	}
 
	public void setX(int x) {
		this.x = x; 
	}
 
	public void setY(int y) {
		this.y = y; 
	}
 
	public String getLabel() {
		return (this.label); 
	}
 
	public int getX() {
		return (this.x); 
	}
 
	public int getY() {
		return (this.y); 
	}	
}

(3)MementoCaretaker充当负责人

package designpatterns.memento;

public class MementoCaretaker {
	private GomanMemento memento;
 
	public GomanMemento getMemento() {
		return memento;
	}
 
	public void setMemento(GomanMemento memento) {
		this.memento = memento;
	}
}

(4)Client客户端

package designpatterns.memento;

public class Client {
	public static void main(String args[]) {
		MementoCaretaker mc = new MementoCaretaker();
		
        Goman go = new Goman("黑1",10,15);
		display(go);

		mc.setMemento(go.save()); //保存状态		
		go.setY(18);
		display(go);

		mc.setMemento(go.save()); //保存状态
		display(go);

		go.setX(16);
		display(go);
		System.out.println("******悔棋******");	

		go.restore(mc.getMemento()); //恢复状态
		display(go);
	}
	
	public static void display(Goman go) {
		System.out.println("棋子" + go.getLabel() + "当前位置为:" + "第" + go.getX() + "行" + "第" + go.getY() + "列。");
	}
}

备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高,因为现在很多基于窗体和浏览器的应用软件并没有提供撤销操作。如果需要为软件提供撤销功能,备忘录模式无疑是一种很好的解决方案。在一些字处理软件、图像编辑软件、数据库管理系统等软件中备忘录模式都得到了很好的应用。
 

各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的各类设计模式实现。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。
 


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【使用ChatGPT自动化】批量转换.csv文件为.xlsx文件

第1次提问: 我:我想使用Python批量转换.csv文件为.xlsx文件,请你提供代码 它:好的,以下是使用Python批量转换.csv文件为.xlsx文件的代码: import os import glob import pandas as pddef csv_to_xlsx(pa…

MATLAB数值运算(六)

目录 实验目的 实验内容 原创代码,仅供参考,不要直接CV呀 ~_~ 实验目的 1)掌握定义符号对象和创建符号表达式的方法; 2)掌握符号运算基本命令和规则; 3)掌握符号表达式的运算法则以及符号矩阵…

A100 Jeston TX1TX2使用教程-接口说明

EdgeBox_Umate_A100是一款嵌入式人工智能计算机,能够为各类终端设备赋予人工智能的能力,降低终端智能的开发门槛。EdgeBox_Umate_A100搭载了移动处理器——NVIDIA Jetson TX1,并且兼容TX2,是最适合边缘计算的高性能平台。 A100 算…

【Javascript - 力扣每日一题】13. 罗马数字转整数

题目描述 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为…

【黑马2023大数据实战教程】VMWare虚拟机部署HDFS集群详细过程

文章目录 部署HDFS集群1.配置workers:2.配置hadoop-env.sh文件3.配置core-site.xml文件4.配置hdfs-site.xml文件准备数据目录分发Hadoop文件夹配置环境变量授权为hadoop用户格式化文件系统错误排查方法!! 视频:黑马2023 VMWare虚拟机部署HDFS集群 注意!这…

linux代码检测工具valgrind之内存检测memcheck

1、安装命令: $ sudo apt-get install valgrind 安装成功如下: 检测版本命令:$ valgrind --version 2、valgrind检测工具tool介绍 (1)Memcheck是一个内存错误检测器。 (2)Cachegrind是缓存…

C++语法(19)---- 模拟AVL树

C语法(18)---- set和map_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130228232?spm1001.2014.3001.5501 目录 1.AVL树的概念 2.节点定义 3.AVL树的类实现 1.类定义 2.insert 1.全代码实现 2.思考角度 3.平衡因…

【Redis】Redis十大数据类型—哈希hash

介绍 Hash 是一个键值对(key - value)集合,其中 value 的形式入:value[{field1,value1},...{fieldN,valueN}]。Hash 特别适合用于存储对象。 Hash和String对象的区别 内部实现 Hash 类型的底…

SpringMVC 接收前端传递的参数

SpringMVC 接受前端传参 1、前端传参需要注意请求的Content-type, 主要使用的有两种: application/x-www-form-urlencodedapplication/json application/x-www-form-urlencoded是浏览器的默认编码格式 ,对于原生的form 表单提交参数,就是使用…

【大数据之Hadoop】十六、MapReduce之Join

1 Reduce Join Map端: 为来自不同表或文件的key/value对,打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。 Reduce端: 在每一个分组当中将那些来源于不同文件的记录…

【剑指 offer】调整数组顺序使奇数位于偶数前面

✨个人主页:bit me👇 ✨当前专栏:算法训练营👇 调 整 数 组 顺 序 使 奇 数 位 于 偶 数 前 面 核心考点:数组操作,排序思想的扩展使用 描述: 输入一个整数数组,实现一个函数来调…

vue2数据响应式原理(3) 带你手写一个defineReactive响应式函数并理解其本质

然后 我们来学一下defineReactive函数 defineReactive其实是一个要声明的函数 基本都是作为一个响应式函数 因为vue的使用比较经典 因此 也成了 响应式的一个代表函数 而定义它的意义在于 defineProperty不好用 具体不好用在哪呢? 我们打开上文用到的项目 将output…

第六讲 循环结构

我们在写程序的时候,极有可能遇到需要重复执行某条指令或某些指令的场景,例如我们需要每隔1秒钟在屏幕上输出一次“hello, world”并持续输出一个小时。如下所示的代码可以完成一次这样的操作,如果要持续输出一个小时,我们就需要把…

Javaee spring jdbctemplate查询数据库,基于纯注解实现

为啥要用纯注解方式呢&#xff1f;因为xml中代码还是有点多&#xff0c;纯注解可以解决该问题 现在要做的很简单&#xff0c;就是用新建的SpringConfig这个类去替代xml 在测试类中加载核心配置类 SpringConfig类中 Configuratio Spring.xml配置类 ComponentScan <!--开…

Linux搭建Web服务器(三)——服务器编程基本框架以及事件处理模式

目录 0x01 服务器编程基本框架 0x02 两种高效的事件处理模式 Reactor 模式 Proactor 模式 模拟Proactor 模式 0x01 服务器编程基本框架 虽然服务器程序的种类繁多&#xff0c;但是其基本框架都是一样的&#xff0c;不同之处是在于处理逻辑。对于我们在这个服务器的搭建可以…

基于Jenkins实现Docker应用的持续集成与部署

先决条件 1. 服务器部署安装有docker 在docker应用开发中最常见的就是开发Dockerfile文件&#xff0c;可以使用代码仓库来管理它。 而在企业私有开发环境中是无法访问公有代码仓库&#xff08;如Github&#xff09;的。这时可以搭建私有代码仓库。 部署安装svn私有仓库 安…

GPT-4 验明真身的三个经典问题:快速区分 GPT-3.5 与 GPT-4

现在已经有很多 ChatGPT 的套壳网站&#xff0c;以下分享验明 GPT-4 真身的三个经典问题&#xff0c;帮助你快速区分套壳网站背后到底用的是 GPT-3.5 还是 GPT-4。 测试问题 1&#xff1a;What is tomorrow in relation to yesterday’s today&#xff1f;&#xff08;昨天的当…

《Shunted Transformer: Shunted Self-Attention》CVPR 2022 oral

论文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Ren_Shunted_Self-Attention_via_Multi-Scale_Token_Aggregation_CVPR_2022_paper.pdf 代码链接&#xff1a;https://github.com/OliverRensu/Shunted-Transformer​ 1. 动机 视觉转换器(ViT)模型…

vscode怎么对选定的代码格式化?ctrl+k,ctrl+f(格式化代码)

先选中代码&#xff1a; 然后按CTRL K 再按CTRLF 也可以先选择要格式化的代码块&#xff0c;ctrlshiftp&#xff0c;搜索format&#xff0c;然后第二个就是&#xff1a;

助你掌握搜索神器,10个实用的Elasticsearch查询技巧

前言 Elasticsearch是一个非常流行的搜索引擎&#xff0c;已经成为了许多企业的首选解决方案。然而&#xff0c;我们要想成为一个优秀的程序员&#xff0c;就必须掌握各种查询技巧。本文将向大家介绍10个实用的Elasticsearch查询技巧&#xff0c;并配上详细的代码示例&#xff…