文章目录
20. 备忘录模式(Memento Pattern)
20.1 需求的引入
游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战 Boss 后攻击力和防御 力下降,从备忘录对象恢复到大战前的状态
20.2 基本介绍
基本介绍
- 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这 个状态。这样以后就可将该对象恢复到原先保存的状态
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意 见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某 种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
- 备忘录模式属于行为型模式
- 备忘录模式的原理类图
- 对原理类图的说明-即(备忘录模式的角色及职责)
- originator:对象(需要保存状态的对象)
- Memento:备忘录对象,负责保存好记录,即 Originator 内部状态
- Caretaker:守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率
- 说明:如果希望保存多个 originator 对象的不同时间的状态,也可以,只需要要 HashMap <String, 集合>
20.3 应用实例
import java.util.ArrayList;
import java.util.List;
/**
* @author houyu
* @createTime 2020/2/9 13:15
*/
public class Demo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState(" 状态#1 攻击力 100 ");
// 保存了当前的状态
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#2 攻击力 80 ");
caretaker.add(originator.saveStateMemento());
originator.setState(" 状态#3 攻击力 50 ");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是 =" + originator.getState());
// 希望得到状态 1, 将 originator 恢复到状态 1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态 1 , 当前的状态是 =" + originator.getState());
/*
* 当前的状态是 = 状态#3 攻击力 50
* 恢复到状态 1 , 当前的状态是 = 状态#1 攻击力 100
*/
}
/**
* 源对象
*/
public static class Originator {
/** 状态信息 */
private String state;
public String getState() { return state; }
public void setState(String state) { this.state = state; }
/**
* 编写一个方法,可以保存一个状态对象 Memento //因此编写一个方法,返回 Memento
*/
public Memento saveStateMemento() {
return new Memento(state);
}
/**
* 通过备忘录对象,恢复状态
*/
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
/**
* 备忘对象
*/
public static class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
/**
* 管理对象
*/
public static class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
/**
* 添加
*/
public void add(Memento memento) {
mementoList.add(memento);
}
/**
* 获取到第 index 个 Originator 的 备忘录对象(即保存状态)
*/
public Memento get(int index) {
return mementoList.get(index);
}
}
}
20.4 备忘录模式的注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
- 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数 据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
21. 解释器模式(Interpreter Pattern)
21.1 需求的引入
四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求
- 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
- 在分别输入 a ,b, c, d, e 的值
- 最后求出结果,如下:
请输入表达式:a+b+c-d+e
请输入a的值:10
请输入b的值:11
请输入c的值:1
请输入d的值:2
请输入e的值:3
运算结果:a+b+c-d+e=23
21.2 基本介绍
基本介绍
- 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法 分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
- 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器, 使用该解释器来解释语言中的句子(表达式)
- 应用场景
3.1 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
3.2 一些重复出现的问题可以用一种简单的语言来表达
3.3 一个简单语法需要解释的场景- 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
-
原理类图
-
对原理类图的说明-即(解释器模式的角色及职责)
- Context:是环境角色,含有解释器之外的全局信息.
- AbstractExpression:抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
- TerminalExpression:为终结符表达式, 实现与文法中的终结符相关的解释操作
- NonTermialExpression:为非终结符表达式,为文法中的非终结符实现解释操作.
- 说明:输入 Context he TerminalExpression 信息通过 Client 输入即可
21.3 应用实例
-
思路分析和图解(类图)
-
源码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
/**
* @author houyu
* @createTime 2020/2/9 22:39
*/
public class Demo {
public static void main(String[] args) throws IOException {
System.out.println("请输入表达式:");
String expStr = new Scanner(System.in).nextLine();
HashMap<String, Integer> var = getValue(expStr);
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for(char ch : expStr.toCharArray()) {
if(ch != '+' && ch != '-') {
if(!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
/**
* 解析器
*/
public static abstract class Expression {
/**
* a + b - c
* 解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value 就是就是具体值
* var {a=10, b=20}
*/
public abstract int interpreter(Map<String, Integer> var);
}
/**
* 变量(值)解析器
*/
public static class VarExpression extends Expression {
/**
* key=a,key=b,key=c
*/
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpreter(Map<String, Integer> var) {
// var 就是{a=10, b=20} // interpreter 根据 变量名称,返回对应值
return var.get(this.key);
}
}
/**
* 符号解析器
*/
public static class SymbolExpression extends Expression {
protected Expression left, right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpreter(Map<String, Integer> var) {
// 因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
return 0;
}
}
/**
* 减法解析器
*/
public static class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(Map<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
/**
* 加法解析器
*/
public static class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(Map<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
public static class Calculator {
private Expression expression;
public Calculator(String expStr) { // expStr = a+b
// 安排运算先后顺序
Stack<Expression> stack = new Stack<>();
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a, +, b]
Expression left = null, right = null;
// 遍历我们的字符数组, 即遍历 [a, +, b]
for(int i = 0; i < charArray.length; i++) {
switch(charArray[i]) {
case '+':
left = stack.pop();// 从 stack 取出 left => "a"
right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
stack.push(new AddExpression(left, right));// 然后根据得到 left 和 right 构建 AddExpresson 加入 stack
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default: //如果是一个 Var 就创建要给 VarExpression 对象,并 push 到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
this.expression = stack.pop();
}
public int run(HashMap<String, Integer> var) {
// 最后将表达式 a+b 和 var = {a=10,b=20}
// 然后传递给 expression 的 interpreter 进行解释执行
return this.expression.interpreter(var);
}
}
}
21.4 解释器模式在 Spring 框架应用的源码剖析
- SpelExpressionParser 代码入口
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("100 * (1+2)");
Integer value = expression.getValue(Integer.class);
System.out.println("value = " + value);
- TemplateAwareExpressionParser#parseExpression
public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
if (context != null && context.isTemplate()) {
return parseTemplate(expressionString, context);
}
else {
return doParseExpression(expressionString, context);
}
}
- InternalSpelExpressionParser#doParseExpression
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
throws ParseException {
try {
this.expressionString = expressionString;
Tokenizer tokenizer = new Tokenizer(expressionString);
this.tokenStream = tokenizer.process();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
Assert.state(ast != null, "No node");
Token t = peekToken();
if (t != null) {
throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected");
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
throw ex.getCause();
}
}
- Tokenizer#process
public List<Token> process() {
while (this.pos < this.max) {
char ch = this.charsToProcess[this.pos];
if (isAlphabetic(ch)) {
lexIdentifier();
}
else {
switch (ch) {
case '+':
if (isTwoCharToken(TokenKind.INC)) {
pushPairToken(TokenKind.INC);
}
else {
pushCharToken(TokenKind.PLUS);
}
break;
case '_': // the other way to start an identifier
lexIdentifier();
break;
case '-':
if (isTwoCharToken(TokenKind.DEC)) {
pushPairToken(TokenKind.DEC);
}
else {
pushCharToken(TokenKind.MINUS);
}
break;
// ...
default:
throw new IllegalStateException("Cannot handle (" + (int) ch + ") '" + ch + "'");
}
}
}
return this.tokens;
}
- OpMultiply#getValueInternal
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Object leftOperand = getLeftOperand().getValueInternal(state).getValue();
Object rightOperand = getRightOperand().getValueInternal(state).getValue();
if (leftOperand instanceof Number && rightOperand instanceof Number) {
Number leftNumber = (Number) leftOperand;
Number rightNumber = (Number) rightOperand;
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
return new TypedValue(leftBigDecimal.multiply(rightBigDecimal));
}
else if (leftNumber instanceof Double || rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
}
else if (leftNumber instanceof Float || rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
return new TypedValue(leftNumber.floatValue() * rightNumber.floatValue());
}
else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
return new TypedValue(leftBigInteger.multiply(rightBigInteger));
}
else if (leftNumber instanceof Long || rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
return new TypedValue(leftNumber.longValue() * rightNumber.longValue());
}
else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNumericOp(rightNumber)) {
this.exitTypeDescriptor = "I";
return new TypedValue(leftNumber.intValue() * rightNumber.intValue());
}
else {
// Unknown Number subtypes -> best guess is double multiplication
return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
}
}
if (leftOperand instanceof String && rightOperand instanceof Integer) {
int repeats = (Integer) rightOperand;
StringBuilder result = new StringBuilder();
for (int i = 0; i < repeats; i++) {
result.append(leftOperand);
}
return new TypedValue(result.toString());
}
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
}
21.5 解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程 序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复 杂、效率可能降低