概述
如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 i : arr) { sum += i; } return sum; }
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。
显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和+-符号组成的合法序列,“1+3-2” 就是这种语言的句子。
解释器就是要解析出来语句的含义。但是如何描述规则呢?
文法(语法)规则:
文法是用于描述语言的语法结构的形式规则。
1 2 3 4 expression ::= value | plus | minus plus ::= expression ‘+’ expression minus ::= expression ‘-’ expression value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
上面规则描述为 :
表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。
抽象语法树:
在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子。
结构 解释器模式包含以下主要角色。
抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
案例实现 【例】设计实现加减法的软件
代码如下:
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 public abstract class AbstractExpression { public abstract int interpret (Context context) ; } public class Value extends AbstractExpression { private int value; public Value (int value) { this .value = value; } @Override public int interpret (Context context) { return value; } @Override public String toString () { return new Integer (value).toString(); } } public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } @Override public int interpret (Context context) { return left.interpret(context) + right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " + " + right.toString() + ")" ; } } public class Minus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Minus (AbstractExpression left, AbstractExpression right) { this .left = left; this .right = right; } @Override public int interpret (Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString () { return "(" + left.toString() + " - " + right.toString() + ")" ; } } public class Variable extends AbstractExpression { private String name; public Variable (String name) { this .name = name; } @Override public int interpret (Context ctx) { return ctx.getValue(this ); } @Override public String toString () { return name; } } public class Context { private Map<Variable, Integer> map = new HashMap <Variable, Integer>(); public void assign (Variable var , Integer value) { map.put(var , value); } public int getValue (Variable var ) { Integer value = map.get(var ); return value; } } public class Client { public static void main (String[] args) { Context context = new Context (); Variable a = new Variable ("a" ); Variable b = new Variable ("b" ); Variable c = new Variable ("c" ); Variable d = new Variable ("d" ); Variable e = new Variable ("e" ); context.assign(a, 1 ); context.assign(b, 2 ); context.assign(c, 3 ); context.assign(d, 4 ); context.assign(e, 5 ); AbstractExpression expression = new Minus (new Plus (new Plus (new Plus (a, b), c), d), e); System.out.println(expression + "= " + expression.interpret(context)); } }
优缺点 1,优点:
易于改变和扩展文法。
由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
实现文法较为容易。
在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
增加新的解释表达式较为方便。
如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。
2,缺点:
使用场景