在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的。
解释器模式的定义
解释器(Interpreter
)模式的定义
:是指给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该 “表示” 来解释语言中的句子。是一种按照规定的语法(文法)进行解析的模式,属于行为型模式。
就比如编译器可以将源码编译解释为机器码,让cpu能进行识别并运行。解释器模式的作用其实与编译器一样,都是将一些固定的文法(即语法)进行解释,构建出一个解释句子的解释器。简单理解,解释器是一个简单语法分析工具,它可以识别句子语义,分离终结符号和非终结符号,提取出需要的信息,让我们能针对不同的信息作出相应的处理。其核心思想是识别文法,构建解释。
解释器模式的结构
解释器模式包含以下主要角色:
1.抽象表达式(AbstractExpression
)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
2.终结符表达式(TerminalExpression
)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。比如:公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符(R1,R2)。
3.非终结符表达式(NonterminalExpression
)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2,解析“+”的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度增加而增加,原则上没法文法规则都对应一个非终结符表达式。
4.环境(Context
)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。比如:R=R1+R2,给R1赋值100,给R2赋值200,这些信息就存放到环境中。
解释器模式的实现
用解释器模式设计一个丹尼斯购物识别vip打折程序。
说明:假如是丹尼斯超市的vip购物就可以打八折,其他超市或者非vip不打折。
public class InterpreterPatternDemo {
public static void main(String[] args){
Context c=new Context();
c.freeRide("丹尼斯的vip用户");
c.freeRide("万达的vip用户");
c.freeRide("胖东来的vip用户");
c.freeRide("丹尼斯的非vip用户");
}
}
//抽象表达式类
interface Expression{
public boolean interpret(String info);//解释方法
}
//终结符表达式类
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]);
}
}
public boolean interpret(String info){
if(set.contains(info)){
return true;
}
return false;
}
}
//非终结符表达式类
class AndExpression implements Expression{
private Expression supermarket=null;
private Expression shopper=null;
public AndExpression(Expression supermarket, Expression shopper) {
this.supermarket = supermarket;
this.shopper = shopper;
}
public boolean interpret(String info){
String s[]=info.split("的");
return supermarket.interpret(s[0])&&shopper.interpret(s[1]);
}
}
//环境类
class Context{
private String[] supermarkets={"丹尼斯"};
private String[] shoppers={"vip用户"};
private Expression expression;
public Context(){
Expression supermarket=new TerminalExpression(supermarkets);
Expression shopper=new TerminalExpression(shoppers);
expression=new AndExpression(supermarket,shopper);
}
public void freeRide(String info){
boolean ok = expression.interpret(info);
if(ok) {
System.out.println("您是"+info+",您本次购物打八折!");
}else {
System.out.println(info+",本次购物不打折!");
}
}
}
程序运行结果如下:
您是丹尼斯的vip用户,您本次购物打八折!
万达的vip用户,本次购物不打折!
胖东来的vip用户,本次购物不打折!
丹尼斯的非vip用户,本次购物不打折!
解释器模式的应用场景
我们程序中,如果存在一种特定类型的问题,该类型问题涉及多个不同的实例,但是具备固定文法描述,那么可以使用解释器模式对该类型问题进行解释,分离出需要的信息,根据获取的信息作出相应的处理.简而言之,对于一些固定文法构建一个解释句子的解释器。适用于以下场景:
1.当语言的文法较为简单,且执行效率不是关键问题时。
2.当问题重复出现,且可以用一种简单的语言来进行表达时。
3.当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。
解释器模式的优缺点
优点:
1.扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
2.容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点:
1.执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
2.会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
3.可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
设计模式篇到此告一段落。。。