解释器模式(interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器用来解释语言中的句子。
比如我们常常会在字符串中搜索匹配的字符或判断一个字符串是否符合我们的规则,此时一般我们会用什么技术?
如判断email、匹配电话号码等。我们会用到正则表达式,而所谓解释器模式,正则表达式就是它的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
解释器模式类图
解释器模式的角色
抽象表达式 (AbstractExpression)
定义解释器接口,解释器的解释操作,主要包含interpret()方法。
终结符表达式 (TermianExpression)
实现与文法中的终结符相关联的解释操作。实现抽象表达式所要求的接口。文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式 (NonterminalExpression)
用来实现文法中与终结符相关的操作,文法中的每条规则都对应一个非终结符表达式。
环境角色(Context)
通常包含各个解释器需要的数据或公共的功能。
客户端 (Client)
客户端代码,构建表示该文法定义的语言中一个特定的句子的抽象语法树,调用解释操作。
举例:
来举一个加减乘除的例子。
上下文(环境)角色,使用HashMap来存储变量对应的数值
public class Context {
private Map valueMap = new HashMap();
public void addValue(Variable x , int y) {
Integer yi = new Integer(y);
valueMap.put(x , yi);
}
public int LookupValue(Variable x) {
int i = ((Integer)valueMap.get(x)).intValue();
return i ;
}
}
抽象表达式角色
public abstract class Expression {
public abstract int interpret(Context con);
}
终结表达式角色
public class Constant extends Expression {
private int i;
public Constant(int i) {
this.i = i;
}
@Override
public int interpret(Context con) {
return i ;
}
}
public class Variable extends Expression {
@Override
public int interpret(Context con) {
return con.LookupValue(this);
}
}
非终结表达式角色
public class Add extends Expression {
private Expression left ,right ;
public Add(Expression left , Expression right) {
this.left = left ;
this.right= right ;
}
@Override
public int interpret(Context con) {
return left.interpret(con) + right.interpret(con);
}
}
public class Subtract extends Expression {
private Expression left , right ;
public Subtract(Expression left , Expression right) {
this.left = left ;
this.right= right ;
}
@Override
public int interpret(Context con) {
return left.interpret(con) - right.interpret(con);
}
}
public class Multiply extends Expression {
private Expression left , right ;
public Multiply(Expression left , Expression right) {
this.left = left ;
this.right= right ;
}
@Override
public int interpret(Context con) {
return left.interpret(con) * right.interpret(con);
}
}
public class Division extends Expression {
private Expression left , right ;
public Division(Expression left , Expression right) {
this.left = left ;
this.right= right ;
}
@Override
public int interpret(Context con) {
try{
return left.interpret(con) / right.interpret(con);
} catch(ArithmeticException ae) {
System.out.println("被除数为0!");
return -11111;
}
}
}
Client角色
public class Test {
private static Expression ex ;
private static Context con ;
public static void main(String[] args) {
con = new Context();
//设置变量、常量
Variable a = new Variable();
Variable b = new Variable();
Constant c = new Constant(2);
//为变量赋值
con.addValue(a , 6);
con.addValue(b , 6);
//运算,对句子的结构由我们自己来分析,构造
ex = new Division(new Multiply(a , b), new Add(new Subtract(a , b) , c));
System.out.println("运算结果为:"+ex.interpret(con));
}
}
运算结果为:18
解释器模式优缺点
优点
1、扩展性好。由于解释器模式中使用类表示语言文法的规则,因此可以通过继承等机制来改变或扩展文法。
2、容易实现。在语法树中的每个表达式节点都是相似的,所以实现其文法较容易。
缺点:
1、执行效率低。解释器模式中通常使用大量的循环和递归调用,当解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
2、会引起类膨胀。解释器模式中每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
3、可应用场景比较少。
应用实例
用解释器模式设计一个只能音乐播放器的程序。
说明:假如我们已知歌手名称和歌曲名称,获取播放歌曲,如果没有歌曲返回暂时没有播放歌曲。
然后,根据文法规则按以下步骤设计
- 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
- 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的歌手或歌曲名称,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
- 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的歌手的终结符表达式对象和满足条件的歌曲的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的歌手中的满足条件的歌曲。
抽象表达式角色
public interface Expression {
public boolean interpret(String info);
}
终结符表达式
public class TerminalExpression implements Expression {
// 存储歌曲名称和歌手
private Set<String> set= new HashSet<String>();
public TerminalExpression(String[] data)
{
for(int i=0;i<data.length;i++) set.add(data[i]);
}
@Override
public boolean interpret(String info) {
if(set.contains(info))
{
return true;
}
return false;
}
}
非终结符表达式
public class AndExpression implements Expression {
private Expression person;
private Expression songName;
public AndExpression(Expression person,Expression songName)
{
this.person=person;
this.songName=songName;
}
@Override
public boolean interpret(String info) {
String s[]=info.split("的");
return person.interpret(s[0])&&songName.interpret(s[1]);
}
}
环境角色
public class Context {
private String [] persons = {"薛之谦","刘德华","陈奕迅"};
private String [] songNames = {"丑八怪","冰雨","十年"};
private Expression personSong;
public Context()
{
Expression person = new TerminalExpression(persons);
Expression songName = new TerminalExpression(songNames);
personSong = new AndExpression(person,songName);
}
public void findSongs(String info)
{
boolean ok = personSong.interpret(info);
if(ok) System.out.println("正在播放"+info+",这首歌曲!");
else System.out.println(info+",音乐播放器暂时没有这首音乐!");
}
}
测试类
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.findSongs("陈奕迅的十年");
context.findSongs("刘德华的冰雨");
context.findSongs("薛之谦的丑八怪");
context.findSongs("周杰伦的青花瓷");
}
}
测试结果
正在播放陈奕迅的十年,这首歌曲!
正在播放刘德华的冰雨,这首歌曲!
正在播放薛之谦的丑八怪,这首歌曲!
周杰伦的青花瓷,音乐播放器暂时没有这首音乐!
模式的应用场景
1、当语言的文法较为简单,且执行效率不是关键问题是。
2、当问题重复出现,且可以用一种简单的语言来进行表达时。
3、当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。
模式的扩展
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
import com.singularsys.jep.*;
public class JepDemo
{
public static void main(String[] args) throws JepException
{
Jep jep=new Jep();
//定义要计算的数据表达式
String 存款利息="本金*利率*时间";
//给相关变量赋值
jep.addVariable("本金",10000);
jep.addVariable("利率",0.038);
jep.addVariable("时间",2);
jep.parse(存款利息); //解析表达式
Object accrual=jep.evaluate(); //计算
System.out.println("存款利息:"+accrual);
}
}
程序运行结果如下:
存款利息:760.0
解释器模式使用情况很少,使用时一定要结合业务进行判断是否符合这种设计模式。