1 解决的问题
编译器、正则表达式、SQL、规则引擎。
2 解释器模式
比命令模式更小众,只有在特定领域才会用到,如编译器、规则引擎、正则表达式、SQL 等,而这类语言又称 领域特定语言 (Domain Specific Language, DSL)。
工作中很少会让我们去编写一个解释器,了解下即可,主要是借鉴思想 如何通过更简洁的规则来表示复杂逻辑。
解释器模式包含四个概念:
- AbstractExpression (抽象表达式) → 定义一个解释器有哪些 解释操作,具体类实现还分 终结符解释器 和 非终结符解释器。
- TerminalExpression (终结符表达式) → 不能被再次拆分的对象,实现 文法中元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一般是文法中的运算单元,如简单公式 R=R1+R2,其中的 R1 和 R2 就是终结符,对应 R1 和 R2 的解释器就是终结符表达式。
- NonterminalExpression (非终结符表达式) → 能够被再次拆分的对象,文法中的每条规则 对应于一个非终结符表达式,一般是文法中的运算符或其他关键字。如 R=R1+R2 里的+就是非终结符,解释+的解释器就是非终结符表达式。它会根据逻辑的复杂度增加而增加,原则上每个文法规则都对应一个非终结符表达式。
- Context (上下文环境) → 存放各个终结符对应的具体值,还是 R=R1+R2,给 R1 赋值 100,给 R2 赋值 200,这些信息需存放到环境角色中,可能会使用栈来存储。
3 具体实现
// 表达式抽象类
interface Expression {
int intercept();
}
// 表达式数字实现类
public class Number implements Expression {
int number;
public Number(char word) {
switch (word) {
case '零': number = 0; break;
case '一': number = 1; break;
case '二': number = 2; break;
case '三': number = 3; break;
case '四': number = 4; break;
case '五': number = 5; break;
case '六': number = 6; break;
case '七': number = 7; break;
case '八': number = 8; break;
case '九': number = 9; break;
default: break;
}
}
@Override public int intercept() { return number; }
}
// 表达式操作符抽象类
abstract class Operator implements Expression {
Expression left;
Expression right;
Operator(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
// 表达式操作符加法实现类
class Add extends Operator {
Add(Expression left, Expression right) { super(left, right); }
@Override public int intercept() { return left.intercept() + right.intercept(); }
}
// 表达式操作符减法实现类
class Sub extends Operator {
Sub(Expression left, Expression right) { super(left, right); }
@Override public int intercept() { return left.intercept() - right.intercept(); }
}
//计算类
class Calculator {
int calculate(String expression) {
Stack<Expression> stack = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char word = expression.charAt(i);
switch (word) {
case '加':
stack.push(new Add(stack.pop(), new Number(expression.charAt(++i))));
break;
case '减':
stack.push(new Sub(stack.pop(), new Number(expression.charAt(++i))));
break;
default:
stack.push(new Number(word));
break;
}
}
return stack.pop().intercept();
}
}
//测试类:
public class Client {
@Test
public void test() {
Calculator calculator = new Calculator();
String expression1 = "一加一";
String expression2 = "一加一加一";
String expression3 = "二加五减三";
String expression4 = "七减五加四减一";
String expression5 = "九减五加三减一";
System.out.println(expression1 + " 等于 " + calculator.calculate(expression1));
System.out.println(expression2 + " 等于 " + calculator.calculate(expression2));
System.out.println(expression3 + " 等于 " + calculator.calculate(expression3));
System.out.println(expression4 + " 等于 " + calculator.calculate(expression4));
System.out.println(expression5 + " 等于 " + calculator.calculate(expression5));
}
}4 优缺点
优点
- 易于实现语法,一条语法用一个解释器对象解释执行;
- 易于扩展新语法,只需创建对应解释器,抽象语法树时使用即可;
缺点
- 可使用场景少,复用性不高,除了发明新的编程语言或对某些新硬件进行解释外,很少用,特定数据结构,扩展性也低;
- 维护成本高,每种规则至少要定义一个解释类,语法规则越多,类越难管理和维护;
- 执行效率低,递归调用方法,解释句子语法复杂时,会执行大量循环语句;
5 应用场景
- 语言语法较为简单,且对执行效率要求不高时,如正则判断 IP 是否合法;
- 问题重复出现,且可用简单语法来进行表达时,如 if-else 统一解释为条件语句;
- 当一个语言需要解释执行时,如 XML 中<>括号标识不同的结点含义;