四则运算问题
通过解释器模式来实现四则运算,如计算a + b - c
的值,具体要求
- 1)先输入表达式的形式,比如
a + b + c - d + e
,要求表达式的字母不能重复
- 2)在分别输入
a, b, c, d, e
的值
- 3)最后求出结果:如图
传统方案解决四则运算问题分析
- 1)编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
- 2)问题分析:如果加入新的运算符,比如
* / (
等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰
- 3)解决方案:可以考虑使用解释器模式,即:表达式 => 解释器(可以有多种) => 结果
解释器模式的基本介绍
1)在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
2)解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
3)应用场景
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表达
- 一个简单语法需要解释的场景
4)这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
原理类图
解释器模式的角色及职责
Context
环境角色:含有解释器之外的全局信息
AbstractExpression
抽象表达式:声明一个抽象的解释操作,该方法为抽象语法树中所有节点共享
TerminalExpression
终结符表达式:实现与文法中终结符相关的解释操作
NonTerminalExpression
非终结符表达式:实现与文法中非终结符相关的解释操作
解释器模式解决四则运算问题
UML 类图
核心代码
抽象表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public abstract class Expression {
public abstract int interpret(Map<String, Integer> var); }
|
抽象运算符号解释器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class SymbolExpression extends Expression { protected Expression left; protected Expression right;
public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; }
@Override public int interpret(Map<String, Integer> var) { return 0; } }
|
加减解释器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); }
@Override public int interpret(Map<String, Integer> var) { return super.left.interpret(var) + super.right.interpret(var); } }
public class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); }
@Override public int interpret(Map<String, Integer> var) { return super.left.interpret(var) - super.right.interpret(var); } }
|
运算器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
public class Calculator {
private Expression expression;
public Calculator(String expStr) { Stack<Expression> stack = new Stack<>(); char[] charArr = expStr.toCharArray();
Expression left; Expression right; for (int i = 0; i < charArr.length; i++) { switch (charArr[i]) { case '+': left = stack.pop(); right = new VarExpression(String.valueOf(charArr[++i])); stack.push(new AddExpression(left, right)); break; case '-': left = stack.pop(); right = new VarExpression(String.valueOf(charArr[++i])); stack.push(new SubExpression(left, right)); break; default: stack.push(new VarExpression(String.valueOf(charArr[i]))); break; } } this.expression = stack.pop(); }
public int run(Map<String, Integer> var) { return this.expression.interpret(var); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void main(String[] args) throws IOException { System.out.print("请输入表达式:"); String expStr = getExpStr(); Map<String, Integer> var = getValue(expStr); Calculator calculator = new Calculator(expStr); System.out.println("运算结果:" + expStr + "=" + calculator.run(var)); }
public static String getExpStr() throws IOException { return new BufferedReader(new InputStreamReader(System.in)).readLine(); }
public static Map<String, Integer> getValue(String expStr) throws IOException { Map<String, Integer> map = new HashMap<>(); String s; for (char ch : expStr.toCharArray()) { s = String.valueOf(ch); if (ch == '+' || ch == '-' || map.containsKey(s)) { continue; } System.out.print("请输入" + s + "的值:"); map.put(s, Integer.valueOf(getExpStr())); } return map; }
|
测试结果
解释器模式在 Spring 框架中的源码分析
Spring
框架中SpelExpressionParser
就使用到解释器模式
示例代码
1 2 3 4
| SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); Expression expression = spelExpressionParser.parseExpression("10*(2+1)*1+66"); int result = (Integer) expression.getValue(); System.out.println(result);
|
UML 类图
角色及职责
1 2 3 4 5 6 7 8 9 10 11 12
| public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { if (context == null) { context = NON_TEMPLATE_PARSER_CONTEXT; }
if (context.isTemplate()) { return parseTemplate(expressionString, context); } else { return doParseExpression(expressionString, context); } }
|
- 调用
parseExpression
方法得到Expression
对象后,调用getValue
解释执行表达式,得到最终结果
解释器模式的注意事项和细节
- 1)当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 2)应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 3)使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低