定义
解释器(Interpreter)模式给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
解释器模式属于类行为型模式。
要点
优点:
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点:
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
主要角色:
抽象表达式(Abstract Expression):定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression):是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式(Nonterminal Expression):也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
环境(Context):通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
场景
解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树。
假如“京津冀”公交车读卡器可以判断乘客的身份,如果是【北京】或者【天津】的 “老人” 、 “儿童” 就可以免费乘车,其他人员乘车一次扣 1 元。
实现
/**
* 抽象表达式类
*/
interface Expression {
boolean interpret(String info);
}
/**
* 终结符表达式类
*/
class TerminalExpression implements Expression {
private Set<String> set = new HashSet<>();
public TerminalExpression(String[] data) {
Collections.addAll(set, data);
}
@Override
public boolean interpret(String info) {
return set.contains(info);
}
}
/**
* 非终结符表达式类
*/
class AndExpression implements Expression {
private Expression city;
private Expression person;
public AndExpression(Expression city, Expression person) {
this.city = city;
this.person = person;
}
@Override
public boolean interpret(String info) {
String[] s = info.split("的");
return city.interpret(s[0]) && person.interpret(s[1]);
}
}
/**
* 环境类
*/
class Context {
private String[] citys = {"北京", "天津"};
private String[] persons = {"老人", "儿童"};
private Expression cityPerson;
public Context() {
Expression city = new TerminalExpression(citys);
Expression person = new TerminalExpression(persons);
cityPerson = new AndExpression(city, person);
}
public void freeRide(String info) {
boolean ok = cityPerson.interpret(info);
if (ok) {
System.out.println("您是" + info + ",您本次乘车免费!");
} else {
System.out.println(info + ",您不是免费人员,本次乘车扣费1元!");
}
}
}
public class Client {
/**
* 文法规则
* <expression> ::= <city>的<person>
* <city> ::= 北京 | 天津
* <person> ::= 老人 | 儿童
*/
public static void main(String[] args) {
Context bus = new Context();
bus.freeRide("北京的老人");
bus.freeRide("北京的中年人");
bus.freeRide("天津的妇女");
bus.freeRide("天津的儿童");
bus.freeRide("河北的中年人");
}
}
----------------输出----------------
您是北京的老人,您本次乘车免费!
北京的年轻人,您不是免费人员,本次乘车扣费1元!
天津的妇女,您不是免费人员,本次乘车扣费1元!
您是天津的儿童,您本次乘车免费!
河北的年轻人,您不是免费人员,本次乘车扣费1元!
源码
总结
适用场景:
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计。