一:解析器模式定义
--->解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的方案,在现在项目中使用较少
--->给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子
二:解析器模式角色
● AbstractExpression——抽象解释器
具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminalExpression完成。
● TerminalExpression——终结符表达式
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到我们例子就是VarExpression类,表达式中的每个终结符都在栈中产生了一个VarExpression对象。
● NonterminalExpression——非终结符表达式
文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分别对应到AddExpression和SubExpression两个类。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
● Context——环境角色
具体到我们的例子中是采用HashMap代替。
三:解析器模式的应用
【1】解析器模式的优点
--->解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
【2】解析器模式的缺点
● 解释器模式会引起类膨胀
-->每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
● 解释器模式采用递归调用方法
-->每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,直到最小的语法单元。
● 效率问题
-->解释器模式由于使用了大量的循环和递归,效率是一个不容忽视的问题,特别是一用于解析复杂、冗长的语法时,效率是难以忍受的。
【3】解析器模式的使用场景
● 重复发生的问题可以使用解释器模式
例如,多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定了。在这种情况下,可以通过程序来一劳永逸地解决该问题。
● 一个简单语法需要解释的场景
为什么是简单?看看非终结表达式,文法规则越多,复杂度越高,而且类间还要进行递归调用(看看我们例子中的栈)。想想看,多个类之间的调用你需要什么样的耐心和信心去排查问题。因此,解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,不过该部分逐渐被专用工具所取代。
在某些特用的商业环境下也会采用解释器模式,我们刚刚的例子就是一个商业环境,而且现在模型运算的例子非常多,目前很多商业机构已经能够提供出大量的数据进行分析。
【4】解析器模式的注意事项
尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。我们在一个银行的分析型项目中就采用JRuby进行运算处理,避免使用解释器模式的四则运算,效率和性能各方面表现良好。
四:解析器模式的最佳实践
解释器模式在实际的系统开发中使用得非常少,因为它会引起效率、性能以及维护等问题,一般在大中型的框架型项目能够找到它的身影,如一些数据分析工具、报表设计工具、科学计算工具等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况,准备使用解释器模式时,可以考虑一下Expression4J、MESP(Math Expression String Parser)、Jep等开源的解析工具包(这三个开源产品都可以通过百度、Google搜索到,请读者自行查询),功能都异常强大,而且非常容易使用,效率也还不错,实现大多数的数学运算完全没有问题,自己没有必要从头开始编写解释器。有人已经建立了一条康庄大道,何必再走自己的泥泞小路呢?
五:解析器模式的案例
【1】抽象表达式类
1 package com.yeepay.sxf.template22; 2 3 import java.util.HashMap; 4 5 /** 6 * 抽象表达式类 7 * @author sxf 8 * 9 */ 10 public abstract class Expression { 11 12 //解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字 13 public abstract int interpreter(HashMap<String, Integer> var); 14 }
【2】变量解析器
1 package com.yeepay.sxf.template22; 2 3 import java.util.HashMap; 4 5 /** 6 * 变量解析器 7 * (运算公式参数) 8 * @author sxf 9 * 10 */ 11 public class VarExpression extends Expression{ 12 //公式中参数名的字母 13 //从HashMap<String,Object>中的key 14 private String key; 15 16 public VarExpression(String key) { 17 super(); 18 this.key = key; 19 } 20 21 @Override 22 public int interpreter(HashMap<String, Integer> var) { 23 return var.get(this.key); 24 } 25 26 }
【3】抽象运算符号解析器
1 package com.yeepay.sxf.template22; 2 /** 3 * 抽象运算符号解析器 4 * @author sxf 5 * 6 */ 7 public abstract class SymbolExpression extends Expression{ 8 //运算符号左侧的表达式 9 protected Expression left; 10 //运算符号右侧的表达式 11 protected Expression right; 12 13 //所有的解析公式都应只关心自己左右两个表达式的结果 14 public SymbolExpression(Expression left,Expression right){ 15 this.left=left; 16 this.right=right; 17 } 18 }
【4】加法解析器
1 package com.yeepay.sxf.template22; 2 3 import java.util.HashMap; 4 5 /** 6 * 加法解析器 7 * @author sxf 8 * 9 */ 10 public class AddExpression extends SymbolExpression{ 11 12 public AddExpression(Expression left, Expression right) { 13 super(left, right); 14 } 15 16 /** 17 * 返回运算结果 18 */ 19 @Override 20 public int interpreter(HashMap<String, Integer> var) { 21 //从参数中解析出加数a 22 int a=super.left.interpreter(var); 23 //从参数中解析出家数b 24 int b=super.right.interpreter(var); 25 //返回运算结果 26 return a+b; 27 } 28 29 30 }
【5】减法解析器
1 package com.yeepay.sxf.template22; 2 3 import java.util.HashMap; 4 /** 5 * 减法解析器 6 * @author sxf 7 * 8 */ 9 public class SubExpression extends SymbolExpression{ 10 11 12 public SubExpression(Expression left, Expression right) { 13 super(left, right); 14 } 15 16 @Override 17 public int interpreter(HashMap<String, Integer> var) { 18 //解析出被减数 19 int left=super.left.interpreter(var); 20 //解析出减数 21 int right=super.right.interpreter(var); 22 //返回运算结果 23 return left-right; 24 } 25 26 27 }
【6】解析器封装类
1 package com.yeepay.sxf.template22; 2 3 import java.util.HashMap; 4 import java.util.Stack; 5 6 /** 7 * 解析器封装类 8 * @author sxf 9 * 10 */ 11 public class Calculator { 12 13 //定义表达式 14 private Expression expression; 15 16 17 //构造函数传参,并解析 18 public Calculator(String expStr){ 19 //定义一个栈,安排运算的先后顺序 20 Stack<Expression> stack=new Stack<Expression>(); 21 //表达式拆分为字符数组 22 char[] charArray=expStr.toCharArray(); 23 //运算 24 Expression left=null; 25 Expression right=null; 26 for(int i=0;i<charArray.length;i++){ 27 switch (charArray[i]) { 28 case '+'://加法 29 left=stack.pop(); 30 right=new VarExpression(String.valueOf(charArray[++i])); 31 stack.push(new AddExpression(left, right)); 32 break; 33 case '-'://减法 34 left=stack.pop(); 35 right=new VarExpression(String.valueOf(charArray[++i])); 36 stack.push(new SubExpression(left, right)); 37 break; 38 default: 39 stack.push(new VarExpression(String.valueOf(charArray[i]))); 40 break; 41 } 42 } 43 this.expression=stack.pop(); 44 } 45 46 //开始运算 47 public int run(HashMap<String, Integer> var){ 48 return this.expression.interpreter(var); 49 } 50 }
【7】客户端测试
1 package com.yeepay.sxf.template22; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.util.HashMap; 7 import java.util.Stack; 8 9 /** 10 * 客户端测试 11 * @author sxf 12 * 13 */ 14 public class Client { 15 16 //运行四则运算 17 public static void main(String[] args) { 18 19 test01(); 20 21 } 22 23 24 /** 25 * 测试堆栈 26 */ 27 public static void test02(){ 28 Stack<String> aStack=new Stack<String>(); 29 aStack.add("1"); 30 aStack.add("2"); 31 aStack.add("3"); 32 33 String aString=aStack.peek(); 34 System.out.println("Client.main()"+aString); 35 36 //aStack.clear(); 37 boolean flag=aStack.empty(); 38 System.out.println("Client.main()"+flag); 39 40 System.out.println("Client.main()"+aStack.pop()); 41 System.out.println("Client.main()"+aStack.pop()); 42 System.out.println("Client.main()"+aStack.pop()); 43 44 } 45 46 47 public static void test01(){ 48 String expStr; 49 try { 50 expStr = getExpStr(); 51 System.out.println("Client.main()"+expStr); 52 HashMap<String, Integer> var=getValue(expStr); 53 Calculator cal=new Calculator(expStr); 54 System.out.println("Client.main(运算结果为:"+expStr+"="+cal.run(var)); 55 } catch (IOException e) { 56 // TODO Auto-generated catch block 57 e.printStackTrace(); 58 } 59 60 } 61 62 //获得表达式 63 public static String getExpStr() throws IOException{ 64 System.out.println("请输入表达式"); 65 return (new BufferedReader(new InputStreamReader(System.in))).readLine(); 66 } 67 68 69 public static HashMap<String, Integer> getValue(String exprStr) throws IOException{ 70 HashMap<String, Integer> map=new HashMap<String, Integer>(); 71 //解析有几个参数要传递 72 for(char ch:exprStr.toCharArray()){ 73 if(ch !='+' && ch!='-'){ 74 //解决重复参数的问题 75 if(!map.containsKey(String.valueOf(ch))){ 76 String in=(new BufferedReader(new InputStreamReader(System.in))).readLine(); 77 map.put(String.valueOf(ch), Integer.valueOf(in)); 78 } 79 } 80 } 81 return map; 82 } 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 }