设计模式 13 解释器模式
解释器模式(Interpreter Pattern)属于行为型模式
概述
解释器模式是指给定一门语言, 基于它的语法, 定义解释器来解释语言中的句子。是一种按照规定的语法进行解析的模式。
就比如编译器可以将源码编译解释为机器码, 让 CPU 能进行识别并运行。解释器模式的作用其实与编译器一样,都是将一些固定的语法进行解释,构建出一个解释句子的解释器。
解释器是一个语法分析工具,它可以识别句子语义,分离终结符号和非终结符号,提取出需要的信息,针对不同的信息做出相应的处理。其核心思想是识别语法,构建解释。
解释器模式主要包含 4 种角色:
-
**抽象表达式(Expression) **:负责定义解释方法
interpret
, 交由子类进行具体解释。 -
终结符表达式(Terminal Expression) :实现文法中与终结符有关的解释操作。
文法中的每一个终结符都有一个具体终结表达式与之相对应,比如公式 R = R1 + R2,R1 和 R2 就是终结符,对应的解析 R1 和 R2 的解释器就是终结符表达式。通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
-
非终结符表达式(Nonterminal Expression):实现文法中与非终结符有关的解释操作。
文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式 R = R1 + R2 中, + 就是非终结符,解析它的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
-
上下文环境(Context) :包含解释器之外的全局信息。
它的任务一般是用来存放文法中各个终结符所对应的具体值,比如 R = R1 + R2,给 R1 赋值 100,给 R2 赋值 200,这些信息需要存放到环境中。
代码实现
这里以根据乘客年龄和身高来判断乘坐公交车是否免费为例介绍解释器模式:
1、定义乘客
/**
* 乘客
*/
public class Passenger {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 身高
*/
private Double height;
public Passenger(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
}
2、定义表达式
/**
* 表达式
*/
public interface Expression {
/**
* 解释年龄
* @param age 年龄
* @return 解释结果
*/
boolean interpret(int age);
/**
* 解释身高
* @param height 身高
* @return 解释结果
*/
boolean interpret(double height);
}
3、定义比较器
/**
* 比较器
*/
public enum Compare {
/**
* 较大
*/
GT,
/**
* 相等
*/
EQ,
/**
* 较小
*/
LT
}
4、定义终结符表达式
/**
* 终结符表达式
*/
public class TerminalExpression implements Expression {
/**
* 年龄
*/
private Integer age;
/**
* 身高
*/
private Double height;
/**
* 比较器
*/
private final Compare compare;
/**
* 构造年龄比较
* @param age 年龄
* @param compare 比较器
*/
public TerminalExpression(int age, Compare compare) {
this.age = age;
this.compare = compare;
}
/**
* 构造身高比较
* @param height 身高
* @param compare 比较器
*/
public TerminalExpression(double height, Compare compare) {
this.height = height;
this.compare = compare;
}
@Override
public boolean interpret(int age) {
// 比较年龄大小
switch (compare) {
// 较大
case GT:
return age > this.age;
// 相等
case EQ:
return age == this.age;
// 较小
case LT:
return age < this.age;
default:
return false;
}
}
@Override
public boolean interpret(double height) {
// 比较身高大小
switch (compare) {
// 较大
case GT:
return height > this.height;
// 相等
case EQ:
return height == this.height;
// 较小
case LT:
return height < this.height;
default:
return false;
}
}
}
5、定义非终结符表达式
与表达式:
/**
* 与表达式
*/
public class AndExpression implements Expression {
/**
* 表达式1
*/
private Expression expression1;
/**
* 表达式2
*/
private Expression expression2;
/**
* 构造表达式
* @param expression1 表达式1
* @param expression2 表达式2
*/
public AndExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(int age) {
return this.expression1.interpret(age) && this.expression2.interpret(age);
}
@Override
public boolean interpret(double height) {
return this.expression1.interpret(height) && this.expression2.interpret(height);
}
}
或表达式:
/**
* 或表达式
*/
public class OrExpression implements Expression {
/**
* 表达式1
*/
private Expression expression1;
/**
* 表达式2
*/
private Expression expression2;
/**
* 构造表达式
* @param expression1 表达式1
* @param expression2 表达式2
*/
public OrExpression(Expression expression1, Expression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
@Override
public boolean interpret(int age) {
return this.expression1.interpret(age) || this.expression2.interpret(age);
}
@Override
public boolean interpret(double height) {
return this.expression1.interpret(height) || this.expression2.interpret(height);
}
}
6、定义免费标准
/**
* 免费标准
*/
public class Free {
/**
* 年龄表达式
*/
private Expression ageExpression;
/**
* 身高表达式
*/
private Expression heightExpression;
/**
* 构造免费情况
* @param age 年龄
* @param height 身高
*/
public Free(int age, double height) {
// 大于等于设定年龄
Expression expression1 = new TerminalExpression(age, Compare.GT);
Expression expression2 = new TerminalExpression(age, Compare.EQ);
ageExpression = new OrExpression(expression1, expression2);
// 小于等于设定身高
expression1 = new TerminalExpression(height, Compare.LT);
expression2 = new TerminalExpression(height, Compare.EQ);
heightExpression = new OrExpression(expression1, expression2);
}
/**
* 结果
* @param age 年龄
* @param height 身高
* @return 判定结果
*/
public boolean result(int age, double height) {
return ageExpression.interpret(age) || heightExpression.interpret(height);
}
}
7、客户端调用
// 定义乘客集合
List<Passenger> list = new ArrayList<>();
Passenger p1 = new Passenger("张三", 65, 170.0);
Passenger p2 = new Passenger("李四", 10, 130.0);
Passenger p3 = new Passenger("王五", 50, 170.0);
list.add(p1);
list.add(p2);
list.add(p3);
// 所有年龄大于等于65或者身高小于等于130的乘客免费乘车
for (Passenger p : list) {
// 定义免费标准
Free free = new Free(65, 130);
// 满足条件则免费
if (free.result(p.getAge(), p.getHeight())) {
System.out.println(p.getName() + ":免费");
}
// 不满足条件则正常收费
else {
System.out.println(p.getName() + ":刷卡或投币");
}
}
输出结果:
张三:免费
李四:免费
王五:刷卡或投币
可以看到,按照预期输出了结果,实现了根据年龄和身高自动判断是否免费的功能。
优缺点
优点
1、扩展性好。解释器模式中使用类来表示语言的文法规则,可以比较方便的通过继承等机制来改变或扩展文法。
2、容易实现。在语法树中的每个表达式节点类都是相似的,实现其文法较为容易。
缺点
1、执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
2、会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护,因此可应用的场景比较少。
使用场景
JDK 源码中的 Pattern 对正则表达式的编译和解析
1、Pattern 有参构造器
private Pattern(String p, int f) {
// 保存数据
pattern = p;
flags = f;
// 如果存在 UNICODE_CHARACTER_CLASS,则使用 UNICODE_CASE
if ((flags & UNICODE_CHARACTER_CLASS) != 0) {
flags |= UNICODE_CASE;
}
// 重置组索引计数
capturingGroupCount = 1;
localCount = 0;
if (!pattern.isEmpty()) {
try {
// 调用编译方法
compile();
} catch (StackOverflowError soe) {
throw error("Stack overflow during pattern compilation");
}
} else {
root = new Start(lastAccept);
matchRoot = lastAccept;
}
}
2、编译方法 compile()
private void compile() {
// 处理规范等价
if (has(CANON_EQ) && !has(LITERAL)) {
normalize();
} else {
normalizedPattern = pattern;
}
patternLength = normalizedPattern.length();
// 为方便起见,将模式复制到 int 数组,使用双零终止模式
temp = new int[patternLength + 2];
hasSupplementary = false;
int c, count = 0;
// 将所有字符转换为代码点
for (int x = 0; x < patternLength; x += Character.charCount(c)) {
c = normalizedPattern.codePointAt(x);
if (isSupplementary(c)) {
hasSupplementary = true;
}
temp[count++] = c;
}
// patternLength 现在在代码点中
patternLength = count;
if (! has(LITERAL)) {
RemoveQEQuoting();
}
// 在这里分配所有临时对象
buffer = new int[32];
groupNodes = new GroupHead[10];
namedGroups = null;
if (has(LITERAL)) {
// 文字模式处理
matchRoot = newSlice(temp, patternLength, hasSupplementary);
matchRoot.next = lastAccept;
} else {
// 开始递归下降解析
matchRoot = expr(lastAccept);
// 检查额外的模式字符
if (patternLength != cursor) {
if (peek() == ')') {
throw error("Unmatched closing ')'");
} else {
throw error("Unexpected internal error");
}
}
}
// 窥孔优化
if (matchRoot instanceof Slice) {
root = BnM.optimize(matchRoot);
if (root == matchRoot) {
root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot);
}
} else if (matchRoot instanceof Begin || matchRoot instanceof First) {
root = matchRoot;
} else {
root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot);
}
// 释放临时存储
temp = null;
buffer = null;
groupNodes = null;
patternLength = 0;
compiled = true;
}
注意事项
解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计。
参考
https://www.bilibili.com/video/BV1u3411P7Na?p=21&vd_source=299f4bc123b19e7d6f66fefd8f124a03