1.定义
解释器模式属于行为模式,定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
定义看上去略显生涩,简单解释一下,其实语言可以理解为编程语言或者表达式,我们需要对语言或者表达式进行解释,搞清楚它要做什么,就需要对语言进行转化,而文法就是他们的的规则和结构,根据文法定义我们的解释器,对语言或者表达式进行解释。而解释器模式就是在我们需要对一些语言进行解释的时候,可以考虑使用它。简单说就是用解释编程语言的方式来解释对象或者表达式的值。
比如要计算类似a+b-c 或者a>b这种表达式,就可以将根据它的文法定义一个对应的解释器,用来计算表达式的最终值,解释器模式正是为解决这种问题诞生的一种编程思想。这种思想在普通应用中用的比较少,一般都是在语言编译解释层面使用,而且非常复杂。
主要角色:
- 抽象表达式角色(Abstract Expression):解释器的顶层接口,定义表达式的解释方法interpret
- 终结符表达式角色(Terminal Expression):抽象表达式的实现,终结符表达式就是实现类似a<b这种表达式,相当于是最底层的操作
- 非终结符表达式(Nonterminal Expression):也是抽象表达式的实现,可以理解为中间表达式,比如a>b && c<d
- 环境角色(Context):上下文对象,一般用来处理解释器需要的数据或者共享的数据,比如参数、中间结果等
UML:
图片来源:百度图库
2.示例
定义一个Person列表,利用解释器模式匹配年龄在18-60之间的人,其实就是计算一个给定的val,判断18≤val && val≥60表达式成立
首先定义抽象表达式角色,其中包含一个表达式
/**
* @description: 抽象表达式角色
* @version: 1.0
*/
public interface AbstractExpression {
boolean interpret(int val);
}
实现终结符表达式,终结符表达式会有具体的符号与之对应
/**
* @description: 终结符表达式
* @version: 1.0
*/
public class TerminalExpression implements AbstractExpression {
private int n;
private String symbol;
public TerminalExpression(int n, String symbol) {
this.n = n;
this.symbol = symbol;
}
@Override
public boolean interpret(int val) {
return symbol.equals("gte") ? val >= n : (symbol.equals("lte") ? val <= n : Boolean.FALSE);
}
}
定义非终结符表达式,分别对应与逻辑和或逻辑,非终结符的表达式计算结果依赖于终结符表达式的计算结果
/**
* @description: 非终结符表达式
* @version: 1.0
*/
public class AndExpression implements AbstractExpression{
private AbstractExpression expression1;
private AbstractExpression expression2;
public AndExpression(AbstractExpression expression1,AbstractExpression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(int val) {
return expression1.interpret(val) && expression2.interpret(val);
}
}
/**
* @description: 非终结符表达式
* @version: 1.0
*/
public class OrExpression implements AbstractExpression{
private AbstractExpression expression1;
private AbstractExpression expression2;
public OrExpression(AbstractExpression expression1,AbstractExpression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(int val) {
return expression1.interpret(val) || expression2.interpret(val);
}
}
定义一个上下文对象,这里简单计算表达式的最终结果
/**
* @description: context
* @version: 1.0
*/
public class Context {
public AbstractExpression expression;
public Context(int left,int right){
AbstractExpression exp1 = new TerminalExpression(left, "gte");
AbstractExpression exp2 = new TerminalExpression(right, "lte");
expression = new AndExpression(exp1, exp2);
}
public boolean operation(int age){
return expression.interpret(age);
}
}
input:
年龄在18-60岁之间,信息:Person{name='张三1', age=36岁}
年龄在18-60岁之间,信息:Person{name='张三3', age=34岁}
年龄在18-60岁之间,信息:Person{name='张三4', age=29岁}
年龄在18-60岁之间,信息:Person{name='张三5', age=38岁}
年龄在18-60岁之间,信息:Person{name='张三6', age=43岁}
年龄在18-60岁之间,信息:Person{name='张三7', age=24岁}
年龄在18-60岁之间,信息:Person{name='张三9', age=24岁}
年龄在18-60岁之间,信息:Person{name='张三10', age=44岁}
年龄在18-60岁之间,信息:Person{name='张三11', age=19岁}
年龄在18-60岁之间,信息:Person{name='张三15', age=31岁}
年龄在18-60岁之间,信息:Person{name='张三17', age=50岁}
年龄在18-60岁之间,信息:Person{name='张三19', age=44岁}
年龄在18-60岁之间,信息:Person{name='张三20', age=34岁}
年龄在18-60岁之间,信息:Person{name='张三24', age=53岁}
年龄在18-60岁之间,信息:Person{name='张三29', age=28岁}
这个例子写完就有点后悔,有点多此一举之嫌…,表达目的不明确。找了另外一个例子做一下补充,该示例单纯的计算了简单的加减法运算,在context类中利用栈实现了无限表达式树的计算,这里似乎更能够体现context的作用
public interface AbstractExpression {
// 每个表达式都必须有一个解释操作
public int interprete(HashMap<String, Integer> var);
}
public class VarExpression implements AbstractExpression {
private String key;
public VarExpression(String key) {
this.key = key;
}
public int interprete(HashMap<String, Integer> var) {
return (Integer) var.get(this.key);
}
}
public abstract class SymbolExpression implements AbstractExpression {
protected AbstractExpression left;
protected AbstractExpression right;
// 非终结符表达式的解释操作只关心自己左右两个表达式的结果
public SymbolExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
}
public class AddExpression extends SymbolExpression {
public AddExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
// 把左右两个表达式运算的结果加起来
public int interprete(HashMap<String, Integer> var) {
return super.left.interprete(var) + super.right.interprete(var);
}
}
public class SubExpression extends SymbolExpression {
public SubExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
// 左右两个表达式相减
public int interprete(HashMap<String, Integer> var) {
return super.left.interprete(var) - super.right.interprete(var);
}
}
public class Calculator {
private AbstractExpression expression;
public Calculator(String expStr) {
// 定义一个堆栈,安排运算的先后顺序
Stack<AbstractExpression> stack = new Stack<AbstractExpression>();
// 表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
// 运算
AbstractExpression left = null;
AbstractExpression 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 calculate(HashMap<String, Integer> var) {
return this.expression.interprete(var);
}
}
public class Test {
public static void main(String[] args) {
// 构造运算元素的值列表
HashMap<String, Integer> ctx = new HashMap<String, Integer>();
ctx.put("a", 10);
ctx.put("b", 20);
ctx.put("c", 30);
ctx.put("d", 40);
ctx.put("e", 50);
ctx.put("f", 60);
Calculator calc = new Calculator("a+b-c");
int result = calc.calculate(ctx);
System.out.println(result);
calc = new Calculator("d-a-b+c");
result = calc.calculate(ctx);
System.out.println(result);
}
}
该示例引自:菜鸟笔记
3.总结
解释器模式对解决类似“表达式解释”这样的工作提供了一个很好的设计思想,优点就是扩展性好,相对容易实现,只需要关注终结表达式和非终结表达式的设计过程即可。其缺点也不难发现,如果需要解释的类型比较多,会出现相当多的表达式子类,而且他们之间的调用关系类似于递归的方式,如果对象过多,也会存在一定的效率问题。