0%

【设计模式】Interpreter Pattern 解释器模式

解释器模式理解剖析以及应用

解释器模式

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。
也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

这里提到的文法和句子的概念同编译原理中的描述相同,

  • “文法”指语言的语法规则,
  • “句子”是语言集中的元素。

例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。

解释器模式提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。

意图

  • 给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

解决问题

对于一些固定文法构建一个解释句子的解释器。

何时使用?

  1. 如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
  2. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  3. 一些重复出现的问题可以用一种简单的语言来进行表达。
  4. 一个简单语法需要解释的场景。

应用环境举例

  1. 编译器、
  2. 运算表达式计算。

优点

  1. 可扩展性比较好,灵活。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 增加了新的解释表达式的方式。
  3. 易于实现简单文法。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

缺点

  1. 可利用场景比较少。 在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
  2. 对于复杂的文法比较难维护。
  3. 解释器模式会引起类膨胀。 解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  4. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。

注意

可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。

相关概念

解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。

文法

文法是用于描述语言的语法结构的形式规则。任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。
例如,中文中的“句子”的文法如下。

1
2
3
4
5
6
7
〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉7大学生I筱霞I英语
〈动词〉::=是|学习
  • 注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

句子

句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。
例如,上述文法可以推出“我是大学生”,所以它是句子。

语法树

语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。
下图所示是“我是大学生”的语法树。

解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式

类图

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

实现

  • 抽象表达式类
    1
    2
    3
    interface AbstractExpression {
    public void interpret(String info); //解释方法
    }
  • 终结符表达式类
    1
    2
    3
    4
    5
    6
    class TerminalExpression implements AbstractExpression {
    public void interpret(String info) {
    //对终结符表达式的处理
    }
    }

  • 非终结符表达式类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class NonterminalExpression implements AbstractExpression {
    private AbstractExpression exp1;
    private AbstractExpression exp2;

    public void interpret(String info) {
    //非对终结符表达式的处理
    }
    }

  • 环境类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Context {
    private AbstractExpression exp;

    public Context() {
    //数据初始化
    }

    public void operation(String info) {
    //调用相关表达式类的解释方法
    }
    }

实例

实例1

用解释器模式设计一个公交车卡的读卡器程序。
假如公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。

1
2
3
<expression> ::= <city>的<person>
<city> ::= 韶关|广州
<person> ::= 老人|妇女|儿童

然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。

  • 定义一个抽象表达式(Expression)接口,
    它包含了解释方法 interpret(String info)。
  • 定义一个终结符表达式(Terminal Expression)类,
    它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
  • 定义一个非终结符表达式(AndExpressicm)类,
    它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
  • 定义一个环境(Context)类,
    它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。

如下图所示

  • 抽象表达式类
    1
    2
    3
    interface Expression {
    public boolean interpret(String info);
    }
  • 终结符表达式类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.util.*;

    class TerminalExpression implements Expression {
    private Set<String> set = new HashSet<String>();

    public TerminalExpression(String[] data) {
    //将传入的数据全部去重插入set中
    for (int i = 0; i < data.length; i++) set.add(data[i]);
    }
    // 在已有的set中寻找,是否有现有的 info元素
    public boolean interpret(String info) {
    if (set.contains(info)) {
    return true;
    }
    return false;
    }
    }
  • 非终结符表达式类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class AndExpression implements Expression {
    private Expression city = null;
    private Expression person = null;

    public AndExpression(Expression city, Expression person) {
    this.city = city;
    this.person = person;
    }
    // 根据语义解释句子
    public boolean interpret(String info) {
    String s[] = info.split("的");
    return city.interpret(s[0]) && person.interpret(s[1]);
    }
    }
  • 环境类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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 + ",您不是免费人员,本次乘车扣费2元!");
    }
    }
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /*文法规则
    <expression> ::= <city>的<person>
    <city> ::= 韶关|广州
    <person> ::= 老人|妇女|儿童
    */
    public class InterpreterPatternDemo {
    public static void main(String[] args) {
    Context bus = new Context();
    bus.freeRide("韶关的老人");
    bus.freeRide("韶关的年轻人");
    bus.freeRide("广州的妇女");
    bus.freeRide("广州的儿童");
    bus.freeRide("山东的儿童");
    }
    }
    结果:
    1
    2
    3
    4
    5
    您是韶关的老人,您本次乘车免费!
    韶关的年轻人,您不是免费人员,本次乘车扣费2元!
    您是广州的妇女,您本次乘车免费!
    您是广州的儿童,您本次乘车免费!
    山东的儿童,您不是免费人员,本次乘车扣费2元!

实例2

创建一个接口 Expression 和实现了 Expression 接口的实体类。
定义作为上下文中主要解释器的 TerminalExpression 类。
其他的类 OrExpression、AndExpression 用于创建组合式表达式。
InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

  • 创建一个表达式接口。
    Expression.java
    1
    2
    3
    public interface Expression {
    public boolean interpret(String context);
    }
  • 创建实现了上述接口的实体类。
    TerminalExpression.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class TerminalExpression implements Expression {

    private String data;

    public TerminalExpression(String data){
    this.data = data;
    }

    @Override
    public boolean interpret(String context) {
    if(context.contains(data)){
    return true;
    }
    return false;
    }
    }
    OrExpression.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class OrExpression implements Expression {

    private Expression expr1 = null;
    private Expression expr2 = null;

    public OrExpression(Expression expr1, Expression expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
    return expr1.interpret(context) || expr2.interpret(context);
    }
    }
    AndExpression.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class AndExpression implements Expression {

    private Expression expr1 = null;
    private Expression expr2 = null;

    public AndExpression(Expression expr1, Expression expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
    return expr1.interpret(context) && expr2.interpret(context);
    }
    }
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class InterpreterPatternDemo {

    //规则:Robert 和 John 是男性
    public static Expression getMaleExpression(){
    Expression robert = new TerminalExpression("Robert");
    Expression john = new TerminalExpression("John");
    return new OrExpression(robert, john);
    }

    //规则:Julie 是一个已婚的女性
    public static Expression getMarriedWomanExpression(){
    Expression julie = new TerminalExpression("Julie");
    Expression married = new TerminalExpression("Married");
    return new AndExpression(julie, married);
    }

    public static void main(String[] args) {
    Expression isMale = getMaleExpression();
    Expression isMarriedWoman = getMarriedWomanExpression();

    System.out.println("John is male? " + isMale.interpret("John"));
    System.out.println("Julie is a married women? "
    + isMarriedWoman.interpret("Married Julie"));
    }
    }
    结果:
    1
    2
    John is male? true
    Julie is a married women? true

感谢查阅