ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

设计模式之解释器模式

设计模式之解释器模式 1.1 解释器模式介绍解释器模式使用频率不算高通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到比如编译器、规则引擎、正则表达式、SQL 解析等。不过了解它的实现原理同样很重要能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。解释器模式(Interpreter pattern)的原始定义是用于定义语言的语法规则表示并提供解释器来处理句子中的语法。我们通过一个例子给大家解释一下解释器模式假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类提供对应的加法和减法的工具方法。//用于两个整数相加的方法 public static int add(int a , int b){ return a b; } //用于三个整数相加的方法 public static int add(int a , int b,int c){ return a b c; } public static int add(Integer ... arr){ int sum 0; for(Integer num : arr){ sum num; } return sum; } -上面的形式比较单一、有限如果形式变化非常多这就不符合要求因为加法和减法运算两个运算符与数值可以有无限种组合方式。比如: 5-32-1, 10-520....文法规则和抽象语法树解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.在上面提到的加法/减法解释器中,每一个输入表达式(比如:234-5) 都包含了3个语言单位,可以使用下面的文法规则定义:文法是用于描述语言的语法结构的形式规则。expression :: value | plus | minus plus :: expression ‘’ expression minus :: expression ‘-’ expression value :: integer注意 这里的符号“::”表示“定义为”的意思竖线 | 表示或左右的其中一个引号内为字符本身引号外为语法。上面规则描述为 表达式可以是一个值也可以是plus或者minus运算而plus和minus又是由表达式结合运算符构成值的类型为整型数。抽象语法树在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 1 2 3 - 4 1 可以通过下面的抽象语法树表示1.2 解释器模式原理解释器模式包含以下主要角色。抽象表达式Abstract Expression角色定义解释器的接口约定解释器的解释操作主要包含解释方法 interpret()。终结符表达式Terminal Expression角色是抽象表达式的子类用来实现文法中与终结符相关的操作文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式.非终结符表达式Nonterminal Expression角色也是抽象表达式的子类用来实现文法中与非终结符相关的操作文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式环境Context角色通常包含各个解释器需要的数据或是公共的功能一般用来传递被所有解释器共享的数据后面的解释器可以从这里获取这些值。客户端Client主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树然后调用解释器的解释方法当然也可以通过环境角色间接访问解释器的解释方法。1.3 解释器模式实现为了更好的给大家解释一下解释器模式, 我们来定义了一个进行加减乘除计算的“语言”语法规则如下运算符只包含加、减、乘、除并且没有优先级的概念表达式中先书写数字后书写运算符空格隔开我们举个例子来解释一下上面的语法规则:比如“ 9 5 7 3 - * ”这样一个表达式我们按照上面的语法规则来处理取出数字“9、5”和“-”运算符计算得到 4于是表达式就变成了“ 4 7 3 * ”。然后我们再取出“4 7”和“ ”运算符计算得到 11表达式就变成了“ 11 3 * ”。最后我们取出“ 11 3”和“ * ”运算符最终得到的结果就是 33。代码示例:用户按照上 面的规则书写表达式传递给 interpret() 函数就可以得到最终的计算结果。/* * 表达式解释器 * */ public class ExpressionInterpreter { //Deque双向队列,可以从队列的两端增加和删除元素 DequeLong numbers new LinkedList(); // 接收表达式进行解析 public long interpreter(String expression){ String[] element expression.split( ); int length element.length; //获取表达式中的数字 for (int i 0; i (length 1)/2 ; i) { //在deque尾部添加元素 numbers.addLast(Long.parseLong(element[i])); } //获取表达式中的运算符号 for (int i (length 1)/2; i length; i) { String operate element[i]; //运算符号必须为 - * /否则抛出异常 boolean isValid .equals(operate) ||-.equals(operate) ||*.equals(operate)||/.equals(operate); if(!isValid){ throw new RuntimeException(Expression is isValid expression); } //pollFirst() 方法,移除Deque队列中的第一个元素并返回被移除的值 Long number1 numbers.pollFirst(); Long number2 numbers.pollFirst(); long result 0; if(operate.equals()){ result number1 number2; } else if (operate.equals(-)) { result number1 - number2; }else if (operate.equals(*)) { result number1 * number2; }else if (operate.equals(/)) { result number1 / number2; } //将运算结果加到集合头部 numbers.addFirst(result); } //运算完的numbers中应该保存的是运算结果,否则是无效表达式 if(numbers.size() ! 1){ throw new RuntimeException(Expression is invalid expression); } //移除第一个元素,并返回 return numbers.pop(); } } /* * 测试 * */ public class TestInterpreter { Test public void client(){ ExpressionInterpreter interpreter new ExpressionInterpreter(); long result interpreter.interpreter(9 5 2 7 - *); System.out.println( 9 5 2 7 - * 表达式的结果为 result); } }代码重构上面代码的所有的解析逻辑都耦合在一个函数中这样显然是不合适的。这 个时候我们就要考虑拆分代码将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式一类是数字一类是运算符运算符又包括加减乘除。 利用解释器模式我们把解析的工作拆分到以下五个类:plu,sub,mul,divNumExpressionPluExpressionSubExpressionMulExpressionDivExpression/* * 表达式接口 * */ public interface Expression { long interpreter(); } /* * 数字表达式 * */ public class NumberExpression implements Expression{ private long number; public NumberExpression(long number) { this.number number; } public NumberExpression(String number) { this.number Long.parseLong(number); } Override public long interpreter() { return this.number; } } /* * 加法表达式 * */ public class PluExpression implements Expression{ private Expression exp1; private Expression exp2; public PluExpression(Expression exp1, Expression exp2) { this.exp1 exp1; this.exp2 exp2; } Override public long interpreter() { return exp1.interpreter() exp2.interpreter(); } } /* * 减法表达式 * */ public class SubExpression implements Expression{ private Expression exp1; private Expression exp2; public SubExpression(Expression exp1, Expression exp2) { this.exp1 exp1; this.exp2 exp2; } Override public long interpreter() { return exp1.interpreter() - exp2.interpreter(); } } /* * 乘法表达式 * */ public class MulExpression implements Expression{ private Expression exp1; private Expression exp2; public MulExpression(Expression exp1, Expression exp2) { this.exp1 exp1; this.exp2 exp2; } Override public long interpreter() { return exp1.interpreter() * exp2.interpreter(); } } /* * 除法表达式 * */ public class DivExpression implements Expression{ private Expression exp1; private Expression exp2; public DivExpression(Expression exp1, Expression exp2) { this.exp1 exp1; this.exp2 exp2; } Override public long interpreter() { return exp1.interpreter() / exp2.interpreter(); } } /* * 表达式解释器类 * */ public class ExpressionInterpreter { //Deque双向队列,可以从队列的两端增加和删除元素 private DequeExpression numbers new LinkedList(); //接收表达式进行解析 public long interpreter(String expression){ //将表达式放入数组 String[] elements expression.split( ); int length elements.length; //获取表达式中的数字 for (int i 0; i (length 1) /2; i) { //在deque队列尾部添加元素 numbers.addLast(new NumberExpression(elements[i])); } //获取表达式中的运算符号 for (int i (length 1) /2; i length ; i) { String operate elements[i]; //运算符号必须为 - * /否则抛出异常 boolean isValid .equals(operate) || -.equals(operate) ||*.equals(operate)||/.equals(operate); if(!isValid){ throw new RuntimeException(无效表达式 expression); } //pollFirst() 方法,移除Deque队列中的第一个元素并返回被移除的值 Expression exp1 numbers.pollFirst(); Expression exp2 numbers.pollFirst(); Expression result null; if(operate.equals()){ result new PluExpression(exp1,exp2); }else if(operate.equals(-)){ result new SubExpression(exp1,exp2); }else if(operate.equals(*)){ result new MulExpression(exp1,exp2); }else if(operate.equals(/)){ result new DivExpression(exp1,exp2); } long num result.interpreter(); //将运算结果加到队列头部 numbers.addFirst(new NumberExpression(num)); } if(numbers.size()!1){ throw new RuntimeException(Expression is invalid expression); } //移除第一个元素,并返回 return numbers.pop().interpreter(); } } /* * 测试 * */ public class TestInterpreter { Test public void Client2(){ ExpressionInterpreter interpreter new ExpressionInterpreter(); long result interpreter.interpreter(6 8 3 2 2 - * /); System.out.println(result); } }ja1.4 解释器模式总结1) 解释器优点易于改变和扩展文法因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言实现文法比较容易在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂增加新的解释表达式比较方便如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则2) 解释器缺点对于复杂文法难以维护在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,导致系统的维护难以管理.执行效率低在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐3) 使用场景当语言的文法比较简单,并且执行效率不是关键问题.当问题重复出现,且可以用一种简单的语言来进行表达当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候
返回列表