设计模式 您所在的位置:网站首页 专员和专家之间还可以增加什么级别 设计模式

设计模式

2024-07-15 04:27| 来源: 网络整理| 查看: 265

备忘录模式:

为了使软件的使用更加人性化,对于误操作,我们需要提供一种类似“后悔药”的机制,让软件系统可以回到误操作前的状态,

因此需要保存用户每一次操作时系统的状态,一旦出现误操作,可以把存储的历史状态取出即可回到之前的状态。

现在大多数软件都有撤销(Undo)的功能,快捷键一般都是Ctrl+Z,目的就是为了解决这个后悔的问题。

备忘录模式是一种给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。

备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,

这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名又叫做Token模式或快照模式(Snapshot Pattern) 。

备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计,其结构如下图所示。

 

Originator:原发器,它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。

Memento:备忘录,存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,

根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。

Caretaker:负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

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

模式分析:

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

package dp.memento; public class Originator { private String state; public Originator(){ } // 创建一个备忘录对象 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 dp.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()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。

在实际开发中,原发器与备忘录之间的关系是非常特殊的,它们要分享信息而不让其他类知道,实现的方法因编程语言的不同而有所差异。

在C++中可以使用friend关键字,让原发器类和备忘录类成为友元类,互相之间可以访问对象的一些私有的属性;

在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包(package)中来实现封装,

在Java语言中可使用默认访问标识符来定义Memento类,让它们之间满足默认的包内可见性,也可以将备忘录类作为原发器类的内部类,

使得只有原发器才可以访问备忘录中的数据,其他对象都无法使用备忘录中的数据。

对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:

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

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

备忘录模式实例:

实例:用户信息操作撤销 某系统提供了用户信息操作模块,用户可以修改自己的各项信息。为了使操作过程更加人性化,现使用备忘录模式对系统进行改进,使得用户在进行了错误操作之后可以恢复到操作之前的状态。

实例结构:

 

 

 

 实例代码:

//============== //备忘录,存储中间状态 class Memento { private String account; private String password; private String telNo; public Memento(String account,String password,String telNo) { this.account=account; this.password=password; this.telNo=telNo; } public String getAccount() { return account; } public void setAccount(String account) { this.account=account; } public String getPassword() { return password; } public void setPassword(String password) { this.password=password; } public String getTelNo() { return telNo; } public void setTelNo(String telNo) { this.telNo=telNo; } } //======================= //负责人类,保管着备忘录类,但不清楚备忘录内部细节 //Client与caretaker交互 public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento=memento; } } //=================== //原发器,可以保存当前状态到备忘录,也可以从备忘录中加载状态 public class UserInfoDTO { private String account; private String password; private String telNo; public String getAccount() { return account; } public void setAccount(String account) { this.account=account; } public String getPassword() { return password; } public void setPassword(String password) { this.password=password; } public String getTelNo() { return telNo; } public void setTelNo(String telNo) { this.telNo=telNo; } public Memento saveMemento() { return new Memento(account,password,telNo); } public void restoreMemento(Memento memento) { this.account=memento.getAccount(); this.password=memento.getPassword(); this.telNo=memento.getTelNo(); } public void show() { System.out.println("Account:" + this.account); System.out.println("Password:" + this.password); System.out.println("TelNo:" + this.telNo); } } //=================== //Client端使用 public class Client { public static void main(String a[]) { UserInfoDTO user=new UserInfoDTO(); Caretaker c=new Caretaker(); user.setAccount("zhangsan"); user.setPassword("123456"); user.setTelNo("13000000000"); System.out.println("状态一:"); user.show(); c.setMemento(user.saveMemento());//保存备忘录 System.out.println("---------------------------"); user.setPassword("111111"); user.setTelNo("13100001111"); System.out.println("状态二:"); user.show(); System.out.println("---------------------------"); user.restoreMemento(c.getMemento());//从备忘录中恢复 System.out.println("回到状态一:"); user.show(); System.out.println("---------------------------"); } }

备忘录模式的注意事项和细节1) 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态2) 实现了信息的封装,使得用户不需要关心状态的保存细节3) 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意4) 适用的应用场景: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 5、数据库的事务管理5) 为了节约内存,备忘录模式可以和原型模式配合使用

解释器模式:

智能手机上的计算器应用,只要输入一个表达式,它就能够计算出表达式结果,如图所示,当输入字符串表达式为“1 + 2 + 3 – 4 + 1”时,将输出计算结果为3。

 

我们知道,像C++、Java和C#等语言无法直接解释类似“1+ 2 + 3 – 4 + 1”这样的字符串(如果直接作为数值表达式时可以解释),因此,计算器应用必须定义一套文法规则来实现对这些语句的解释。

假如要开发一套机器人控制程序,提供图形化的设置界面,用户通过对界面进行操作可以创建一个机器人控制指令,机器人在收到指令后将按照指令的设置进行移动,

例如输入控制指令:up move 5,则“向上移动5个单位”;输入控制指令:down run 10 and left move 20,则“向下快速移动10个单位再向左移动20个单位”。

开发人员就要编写代码来解析控制指令的文法规则,并按指令要求来驱动机器人完成相应动作。

如果在系统中某一特定类型的问题发生的频率很高,此时可以考虑将这些问题的实例表述为一个语言中的句子,然后可以构建一个解释器,该解释器通过解释这些句子来解决这些问题。

在实际开发中,这些简单的自定义语言可以基于现有的编程语言来设计,如果所基于的编程语言是面向对象语言,此时可以使用解释器模式来实现自定义语言。

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。

在前面所提到的加法/减法解释器中,每一个输入表达式,例如“1 + 2 + 3 – 4 + 1”,都包含了三个语言单位,可以使用如下文法规则来定义:

expression ::= value | operation operation ::= expression '+' expression | expression '-' expression value ::= an integer //一个整数值

该文法规则包含三条语句,第一条表示表达式的组成方式,其中value和operation是后面两个语言单位的定义

每一条语句所定义的字符串如operation和value称为语言构造成分或语言单位,符号“::=”表示“定义为”的意思,其左边的语言单位通过右边来进行说明和定义,语言单位对应终结符表达式和非终结符表达式。

如本规则中的operation是非终结符表达式,它的组成元素仍然可以是表达式,可以进一步分解,而value则是终结符表达式。

在文法规则定义中可以使用一些符号来表示不同的含义,如使用“|”表示或,使用“{”和“}”表示组合,使用“*”表示出现0次或多次等,其中使用频率最高的符号是表示“或”关系的“|”,

如文法规则“boolValue ::= 0 | 1”表示终结符表达式boolValue的取值可以为0或者1。

除了使用文法规则来定义一个语言,在解释器模式中还可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,

每一棵抽象语法树对应一个语言实例,如加法/减法表达式语言中的语句“1+ 2 + 3 – 4 + 1”,可以通过如图所示抽象语法树来表示:

在该抽象语法树中,可以通过终结符表达式value和非终结符表达式operation组成复杂的语句,每个文法规则的语言实例都可以表示为一个抽象语法树,

即每一条具体的语句都可以用类似上图所示的抽象语法树来表示,在图中终结符表达式类的实例作为树的叶子节点,而非终结符表达式类的实例作为非叶子节点,

它们可以将终结符表达式类的实例以及包含终结符和非终结符实例的子表达式作为其子节点。抽象语法树描述了如何构成一个复杂的句子,

通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类。

解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它用于描述如何使用面向对象语言构成一个简单的语言解释器。

在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。

此时,可以使用解释器模式来设计这种新的语言。对解释器模式的学习能够加深我们对面向对象思想的理解,并且掌握编程语言中文法规则的解释过程。

模式定义:

解释器模式(Interpreter Pattern) :定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码,解释器模式是一种类行为型模式。

 

AbstractExpression: 抽象表达式声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。

TerminalExpression: 终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。

通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。

NonterminalExpression: 非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,

也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。

Context: 环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

Client: 客户类,指的是使用解释器的客户端,通常在这里把遵循语言文法规则的表达式转换成为使用解释器对象描述的抽象语法树,然后调用解释操作。建树的操作也可以封装在Context环境类中。

模式分析:

在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一条文法规则,所以系统将具有较好的灵活性和可扩展性。

对于所有的终结符和非终结符,我们首先需要抽象出一个公共父类,即抽象表达式类,其典型代码如下所示:

public abstract class AbstractExpression { public abstract void interpret(Context ctx); }

终结符表达式和非终结符表达式类都是抽象表达式类的子类,对于终结符表达式,其代码很简单,主要是对终结符元素的处理,其典型代码如下所示:

public class TerminalExpression extends AbstractExpression { public void interpret(Context ctx) { //对于终结符表达式的解释操作 } }

对于非终结符表达式,其代码相对比较复杂,因为可以通过非终结符将表达式组合成更加复杂的结构,对于包含两个操作元素的非终结符表达式类,其典型代码如下

public class NonterminalExpression extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public NonterminalExpression(AbstractExpression left,AbstractExpression right) { this.left=left; this.right=right; } public void interpret(Context ctx) { //递归调用每一个组成部分的interpret()方法 //在递归调用时指定组成部分的连接方式,即非终结符的功能 } }

环境类Context用于存储一些全局信息,通常包含一个HashMap或ArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,

如变量名与值的映射关系(key/value)等,用于在进行具体的解释操作时从中获取相关信息。其典型代码如下:

public class Context { private HashMap map = new HashMap(); public void assign(String key, String value) { //往环境类中设值 } public String lookup(String key) { //获取存储在环境类中的值 } }

客户类Client的典型代码如下:

public class Client { //主要按照文法规则对特定的句子构建抽象语法树 //然后调用解释操作 }

模式分析:

解释器模式的功能:解释器模式使用解释器对象来表示和处理相应的文法规则,一般一个解释器对象处理一条文法规则。

理论上来说,只要能用解释器对象把符合文法规则的表达式表示出来,而且能够构成抽象的语法树,那都可以使用解释器模式来处理。

文法规则和解释器之间是有对应关系的,一般一个解释器处理一条文法规则,但是反过来并不成立,一条文法规则是可以有多种解释和处理的,也就是一条文法规则可以对应多个解释器对象。

上下文在解释器模式中起到非常重要的作用,由于上下文会被传递到所有的解释器中,因此可以在上下文中存储和访问解释器的状态,比如前面的解释器可以存储一些数据在上下文中,后面的解释器就可以获取这些值。

另外还可以通过上下文传递一些在解释器外部,但是解释器需要的数据,也可以是一些全局的,公共的数据。

上下文还有一个功能,就是可以提供所有解释器对象的公共功能,类似于对象组合,而不是使用继承来获取公共功能,在每个解释器对象里面都可以调用。

在客户端构建抽象语法树,往往是比较麻烦的,但是在解释器模式中,并没有涉及这部分功能,只是负责对构建好的抽象语法树进行解释处理。

如果文法规则简单,构建抽象语法树就不是特别困难的事,如果文法规则特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨,解释器模式就不太适合。

解释器模式实例:

实例:数学运算解释器,现需要构造一个语言解释器,使得系统可以执行整数间的乘、除和求模运算。如用户输入表达式“3 * 4 / 2 % 4”,输出结果为2。使用解释器模式实现该功能。

实例结构:

 

实例代码:

//================= public interface Node { public int interpret(); } //=============== public class ValueNode implements Node { private int value; public ValueNode(int value) { this.value=value; } public int interpret() { return this.value; } } //====================== public class DivNode extends SymbolNode { public DivNode(Node left,Node right) { super(left,right); } public int interpret() { return super.left.interpret() / super.right.interpret(); } } //===================== public abstract class SymbolNode implements Node { protected Node left; protected Node right; public SymbolNode(Node left,Node right) { this.left=left; this.right=right; } } //==================== public class ModNode extends SymbolNode { public ModNode(Node left,Node right) { super(left,right); } public int interpret() { return super.left.interpret() % super.right.interpret(); } } //===================== public class MulNode extends SymbolNode { public MulNode(Node left,Node right) { super(left,right); } public int interpret() { return super.left.interpret() * super.right.interpret(); } } //=========================== public class DivNode extends SymbolNode { public DivNode(Node left,Node right) { super(left,right); } public int interpret() { return super.left.interpret() / super.right.interpret(); } } //=========================== import java.util.*; public class Calculator { private String statement; private Node node; public void build(String statement) { Node left=null,right=null; Stack stack=new Stack(); String[] statementArr=statement.split(" "); for(int i=0;i=1000) { acc.setState(new HighState(this)); } else if(point>=100) { acc.setState(new MiddleState(this)); } } } //================= public class MiddleState extends AbstractState { public MiddleState(AbstractState state) { this.acc=state.acc; this.point=state.getPoint(); this.stateName="高手"; } public void writeNote(int score) { System.out.println(acc.getName() + "发布留言" + ",增加" + score + "*2个积分。"); this.point+=score*2; checkState(score); System.out.println("剩余积分为:" + this.point + ",当前级别为:" + acc.getState().stateName + "。"); } public void checkState(int score) { if(point>=1000) { acc.setState(new HighState(this)); } else if(point


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有