【Java设计模式】行为型设计模式-解释器模式(二十三)

129 阅读6分钟

代码GitHub:github.com/lanjie6/Des…

解释器模式

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

进一步阐述:

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

解释器模式包含五种角色:

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

案例:用解释器模式设计一个“天府通”公交车卡的读卡器程序。其中抽象表达式Expression接口就是抽象表达式角色,解释器终结符表达TerminalExpression类就是终结符表达式角色,解释器非终结符表达AndExpression类就是非终结符表达式角色,环境Context类就是环境角色,客户端Client类就是客户端。

UML类图:

解释器模式.jpg

客户端Client类:

/**
 * 使用解释器模式的客户端
 */
public class Client {
​
    public static void main(String[] args) {
        Context bus = new Context();
        bus.freeRide("成都的老人");
        bus.freeRide("成都的年轻人");
        bus.freeRide("广州的妇女");
        bus.freeRide("广州的儿童");
        bus.freeRide("北京的儿童");
    }
}

环境Context类:

/**
 * 环境类
 */
public class Context {
​
    //定义免费解析的城市
    private String[] cities = {"成都", "广州"};
​
    //定义解析的人群
    private String[] persons = {"老人", "妇女", "儿童"};
​
    private Expression cityPerson;
​
    public Context() {
        Expression city = new TerminalExpression(cities);
        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元!");
        }
    }
}

抽象表达式Expression接口:

/**
 * 抽象表达式类
 */
public interface Expression {
​
    /**
     * 解释方法
     */
    boolean interpret(String info);
}

解释器终结符表达TerminalExpression类:

/**
 * 解释器终结符表达类
 */
public class TerminalExpression implements Expression {
​
    private Set<String> set = new HashSet<>();
​
    public TerminalExpression(String[] data) {
        set.addAll(Arrays.asList(data));
    }
​
    public boolean interpret(String info) {
        return set.contains(info);
    }
}

解释器非终结符表达AndExpression类:

/**
 * 解释器非终结符表达类
 */
public class AndExpression implements Expression {
​
    //地市
    private Expression city;
​
    //人群
    private Expression person;
​
    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]);
    }
}

运行结果:

解释器模式运行结果.png

总结:

  • 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  • 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
  • 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  • 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  • 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

典型运用场景举例:

  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
  • 解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。
  • 如果碰到对表达式的解释,在 Java中可以用 Expression4J 或 Jep 等来设计。

设计模式相关文章