真刀实枪之解释器模式
- 从模型公式说起
- 需求,输入一个模型公式,然后输入模型中的参数,运算出结果
- 设计要求
- 公式可以运行时编辑
- 高扩展性
- 效率可以暂不考虑
- 分析下这个需求,还是比较简单的,就是有一套模板,然后填入参数,计算出相应的结果
- 这个需求中的角色主要有两个
- 运算元素【终结符号:这个元素除了赋值以外,不需要做其他的任何处理】
- 运算法则【非终结符号:编写算法进行处理】
- 这个需求中的角色主要有两个
- 运算元素与运算符号的异同点
- 共同点:都要被解析
- 不同点:运算元素有相同的功能,可以归为一类;运算符要被分别处理,用不同类来处理
- 那就从加减模型开始这次的设计之旅吧
- 加减模型简单类图
- 看上面这个类图,有什么问题没有?对,运算有先后顺序,加减法可能用不到,但是考虑到扩展性后,乘除加上后就不一样了。好了,问题有了,那么开始解决问题吧!
- 总的思路有了,现在把细节完善一下吧
- 加减模型简单类图
-
设计基本完成,现在动手实现具体的逻辑吧
-
代码
-
Expression
package com.peng.js; import java.util.HashMap; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public abstract class Expression { public abstract int interpreter(HashMap<String, Integer> var); }
-
VarExpression
package com.peng.js; import java.util.HashMap; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class VarExpression extends Expression { private String key; public VarExpression(String key) { super(); this.key = key; } // 从map中取值 @Override public int interpreter(HashMap<String, Integer> var) { return var.get(key); } }
-
SymbolExpression
package com.peng.js; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public abstract class SymbolExpression extends Expression { protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { super(); this.left = left; this.right = right; } }
-
AddExpression
package com.peng.js; import java.util.HashMap; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); } // 把左右两边相加 @Override public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) + super.right.interpreter(var); } }
-
SubExpression
package com.peng.js; import java.util.HashMap; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); } // 把左右两边相加 @Override public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) - super.right.interpreter(var); } }
-
Calculator
package com.peng.js; import java.util.HashMap; import java.util.Stack; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class Calculator { // 定义表达式 private Expression expression; // 构造函数传参,并解析 public Calculator(String expStr) { super(); // 定义一个栈,安排运算的先后顺序 Stack<Expression> stack = new Stack<Expression>(); // 表达式拆分为字符数组 char[] charArray = expStr.toCharArray(); // 运算 Expression left = null; Expression right = null; for (int i = 0; i < charArray.length; i++) { switch (charArray[i]) { case '+': {// 加法 // 加法结果放到栈中 left = stack.pop(); right = new VarExpression(String.valueOf(charArray[++i])); stack.push(new AddExpression(left, right)); break; } case '-': {// 减法 // 减法结果放到栈中 left = stack.pop(); right = new VarExpression(String.valueOf(charArray[++i])); stack.push(new SubExpression(left, right)); break; } default: { // 公式中的变量 stack.push(new VarExpression(String.valueOf(charArray[i]))); } } } this.expression = stack.pop(); } // 开始运算 public int run(HashMap<String, Integer> var) { return this.expression.interpreter(var); } }
-
Client
package com.peng.js; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class Client { public static void main(String[] args) throws IOException { String expStr = getExpStr(); // 赋值 HashMap<String, Integer> var = getValue(expStr); Calculator cal = new Calculator(expStr); // 运算结果 System.out.println("表达式:" + expStr + "的值=" + cal.run(var)); } private static HashMap<String, Integer> getValue(String expStr) throws IOException { HashMap<String, Integer> map = new HashMap<String, Integer>(); // 解析有几个参数要传递 for (char ch : expStr.toCharArray()) { if (ch != '+' && ch != '-') { // 解决重复参数的问题 if (!map.containsKey(String.valueOf(ch))) { System.out.println("请输入" + String.valueOf(ch) + "参数的值:"); String in = new BufferedReader(new InputStreamReader( System.in)).readLine(); map.put(String.valueOf(ch), Integer.valueOf(in)); } } } return map; } // 获得表达式 private static String getExpStr() throws IOException { System.out.println("请输入表达式:"); return new BufferedReader(new InputStreamReader(System.in)).readLine(); } }
-
执行结果
请输入表达式: a+b+c 请输入a参数的值: 1 请输入b参数的值: 2 请输入c参数的值: 3 表达式:a+b+c的值=6
-
-
解释器模式的定义
- Interpreter Pattern
- Given a language,define a representation for its grammer along with an interpret uses the representation to interpretwntences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子)
解释器模式的通用类图
- 解释
- AbstractExpression:抽象解释器
- TerminalExpression:终结符表达式【eg:参数】
- NonterminalExpression:非终结符表达式【eg:符号】
- Context:环境角色
通用代码
-
AbstractExpression
package js2; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public abstract class Expression { // 解析任务 public abstract Object interpreter(Context ctx); }
-
TerminalExpression
package js2; /** * @author kungfu~peng * @data 2017年12月3日 * @description 终结符表达式 */ public class TerminalExpression extends Expression { // 通常终结符表达式只有一个,但是有多个对象 @Override public Object interpreter(Context ctx) { return null; } }
-
NonterminalExpression
package js2; /** * @author kungfu~peng * @data 2017年12月3日 * @description 非终结符表达式 */ public class NonterminalExpression extends Expression { // 每个非终结符表达式都会对其他表达式产生依赖 public NonterminalExpression(Expression... expression) { super(); } @Override public Object interpreter(Context ctx) { // 进行文法处理 return null; } }
-
Context
package js2; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class Context { }
-
Client
package js2; import java.util.Stack; /** * @author kungfu~peng * @data 2017年12月3日 * @description */ public class Client { public static void main(String[] args) { Context context = new Context(); // 通常定义一个语法容器,容纳一个具体的表达式--ListArray,LinkedList,Stack等类型 Stack<Expression> stack = null; for (;;) { // 进行语法树判断,并产生递归调用 } Expression exp = stack.pop(); // 具体元素进入场景 exp.interpreter(context); } }
解释器模式的应用
- 解释器模式的优点
- 语法分析工具
- 扩展性好
- 解释器模式的缺点
- 引起类的膨胀
- 采用递归--调用复杂
- 效率问题【循环和递归引起的】
解释器模式的使用场景
- 重复发生的问题【服务器日志的处理】
- 一个简单语法需要解释的场景【简单:减少循环和递归(SQL语法分析)】
解释器模式使用的注意事项
- 尽量不要在重要的模块使用--维护是个大问题
- 可以代替解释器模式的技术:shell,JRuby,Groovy等脚本语言
最佳实践
- 实际开发中使用的非常少:考虑效率、性能、维护等问题
- 在大中型的框架中可以看到--数据分析、报表设计、科学计算工具
- 站在巨人的肩膀【开源解析工具包】
- Expression4J
- MESP
- JEP
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;