Interpreter Design Pattern in Java – Java中的解释器设计模式

最后修改: 2018年 7月 3日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll introduce one of the behavioral GoF design patterns – the Interpreter.

在本教程中,我们将介绍GoF的行为设计模式之一–解释器。

At first, we’ll give an overview of its purpose and explain the problem it tries to solve.

首先,我们将对其目的进行概述,并解释它试图解决的问题。

Then, we’ll have a look at Interpreter’s UML diagram and implementation of the practical example.

然后,我们将看一下Interpreter的UML图和实际例子的实现。

2. Interpreter Design Pattern

2.解释器设计模式

In short, the pattern defines the grammar of a particular language in an object-oriented way which can be evaluated by the interpreter itself.

简而言之,该模式以面向对象的方式定义了特定语言的语法,可以由解释器本身进行评估。

Having that in mind, technically we could build our custom regular expression, a custom DSL interpreter or we could parse any of the human languages, build abstract syntax trees and then run the interpretation.

考虑到这一点,从技术上讲,我们可以建立我们的自定义正则表达式、自定义DSL解释器,或者我们可以解析任何一种人类语言,建立抽象的语法树,然后运行解释。

These are only some of the potential use cases, but if we think for a while, we could find even more usages of it, for example in our IDEs, since they’re continually interpreting the code we’re writing and thus supplying us with priceless hints.

这些只是一些潜在的用例,但如果我们想一想,我们可以发现它的更多用途,例如在我们的IDE中,因为它们在不断地解释我们正在写的代码,从而为我们提供无价的提示。

The interpreter pattern generally should be used when the grammar is relatively simple.

解释器模式一般应在语法相对简单时使用。

Otherwise, it might become hard to maintain.

否则,它可能会变得难以维持。

3. UML Diagram

3.UML图示

Interpreter

Above diagram shows two main entities: the Context and the Expression.

上图显示了两个主要实体:ContextExpression

Now, any language needs to be expressed in some way, and the words (expressions) are going to have some meaning based on the given context.

现在,任何语言都需要以某种方式进行表达,而这些词语(表达方式)要根据给定的语境具有一定的意义。

AbstractExpression defines one abstract method which takes the context as a parameter. Thanks to that, each expression will affect the context, change its state and either continue the interpretation or return the result itself.

AbstractExpression 定义了一个抽象的方法,该方法将上下文作为参数。得益于此,每个表达式将影响上下文,改变其状态,并继续解释或返回结果本身。

Therefore, the context is going to be the holder of the global state of processing, and it’s going to be reused during the whole interpretation process.

因此,上下文将是全局处理状态的持有者,它将在整个解释过程中被重复使用。

So what’s the difference between the TerminalExpression and NonTerminalExpression?

那么,TerminalExpressionNonTerminalExpression之间有什么区别?

A NonTerminalExpression may have one or more other AbstractExpressions associated in it, therefore it can be recursively interpreted. In the end, the process of interpretation has to finish with a TerminalExpression that will return the result.

一个NonTerminalExpression可以有一个或多个其他AbstractExpressions关联,因此它可以被递归解释。最后,解释的过程必须以一个终端表达式结束,并返回结果。

It’s worth to note that NonTerminalExpression is a composite.

值得注意的是,NonTerminalExpression是一个composite

Finally, the role of the client is to create or use an already created abstract syntax tree, which is nothing more than a sentence defined in the created language.

最后,客户的作用是创建或使用已经创建的抽象语法树,它不过是在创建的语言中定义的句子。

4. Implementation

4.实施

To show the pattern in action, we’ll build a simple SQL-like syntax in an object-oriented way, which will be then interpreted and return us the result.

为了展示该模式的作用,我们将以面向对象的方式建立一个简单的类似SQL的语法,然后对其进行解释并返回给我们结果。

First, we’ll define Select, From, and Where expressions, build a syntax tree in the client’s class and run the interpretation.

首先,我们将定义Select, From, Where表达式,在客户的类中建立一个语法树并运行解释。

The Expression interface will have the interpret method:

Expression接口将有解释方法。

List<String> interpret(Context ctx);

Next, we define the first expression, the Select class:

接下来,我们定义第一个表达式,即Select类。

class Select implements Expression {

    private String column;
    private From from;

    // constructor

    @Override
    public List<String> interpret(Context ctx) {
        ctx.setColumn(column);
        return from.interpret(ctx);
    }
}

It gets the column name to be selected and another concrete Expression of type From as parameters in the constructor.

它在构造函数中获得要选择的列名和另一个具体的Expression类型的From作为参数。

Note that in the overridden interpret() method it sets the state of the context and passes the interpretation further to another expression along with the context.

请注意,在重载的interpret()方法中,它设置了上下文的状态,并将解释与上下文一起进一步传递给另一个表达。

That way, we see that it’s a NonTerminalExpression.

这样,我们就能看到它是一个NonTerminalExpression.

Another expression is the From class:

另一种表达是From类。

class From implements Expression {

    private String table;
    private Where where;

    // constructors

    @Override
    public List<String> interpret(Context ctx) {
        ctx.setTable(table);
        if (where == null) {
            return ctx.search();
        }
        return where.interpret(ctx);
    }
}

Now, in SQL the where clause is optional, therefore this class is either a terminal or a non-terminal expression.

现在,在SQL中,where子句是可选的,因此这个类是终端或非终端表达式。

If the user decides not to use a where clause, the From expression it’s going to be terminated with the ctx.search() call and return the result. Otherwise, it’s going to be further interpreted.

如果用户决定不使用where子句,From表达式将被ctx.search()调用终止并返回结果。否则,它将会被进一步解释。

The Where expression is again modifying the context by setting the necessary filter and terminates the interpretation with search call:

Where表达式通过设置必要的过滤器再次修改上下文,并通过搜索调用终止解释。

class Where implements Expression {

    private Predicate<String> filter;

    // constructor

    @Override
    public List<String> interpret(Context ctx) {
        ctx.setFilter(filter);
        return ctx.search();
    }
}

For the example, the Context class holds the data which is imitating the database table.

对于这个例子,Context 类持有模仿数据库表的数据。

Note that it has three key fields which are modified by each subclass of Expression and the search method:

请注意,它有三个关键字段,由Expression的每个子类和搜索方法修改。

class Context {

    private static Map<String, List<Row>> tables = new HashMap<>();

    static {
        List<Row> list = new ArrayList<>();
        list.add(new Row("John", "Doe"));
        list.add(new Row("Jan", "Kowalski"));
        list.add(new Row("Dominic", "Doom"));

        tables.put("people", list);
    }

    private String table;
    private String column;
    private Predicate<String> whereFilter;

    // ... 

    List<String> search() {

        List<String> result = tables.entrySet()
          .stream()
          .filter(entry -> entry.getKey().equalsIgnoreCase(table))
          .flatMap(entry -> Stream.of(entry.getValue()))
          .flatMap(Collection::stream)
          .map(Row::toString)
          .flatMap(columnMapper)
          .filter(whereFilter)
          .collect(Collectors.toList());

        clear();

        return result;
    }
}

After the search is done, the context is clearing itself, so the column, table, and filter are set to defaults.

搜索完成后,上下文正在清除自己,所以列、表和过滤器被设置为默认值。

That way each interpretation won’t affect the other.

这样一来,每一种解释都不会影响另一种解释。

5. Testing

5.测试

For testing purposes, let’s have a look at the InterpreterDemo class:

为了测试,让我们看一下InterpreterDemo类。

public class InterpreterDemo {
    public static void main(String[] args) {

        Expression query = new Select("name", new From("people"));
        Context ctx = new Context();
        List<String> result = query.interpret(ctx);
        System.out.println(result);

        Expression query2 = new Select("*", new From("people"));
        List<String> result2 = query2.interpret(ctx);
        System.out.println(result2);

        Expression query3 = new Select("name", 
          new From("people", 
            new Where(name -> name.toLowerCase().startsWith("d"))));
        List<String> result3 = query3.interpret(ctx);
        System.out.println(result3);
    }
}

First, we build a syntax tree with created expressions, initialize the context and then run the interpretation. The context is reused, but as we showed above, it cleans itself after each search call.

首先,我们用创建的表达式建立一个语法树,初始化上下文,然后运行解释。上下文是重复使用的,但正如我们上面所显示的,它在每次搜索调用后都会自我清理。

By running the program, the output should be as follow:

通过运行该程序,输出应该如下。

[John, Jan, Dominic]
[John Doe, Jan Kowalski, Dominic Doom]
[Dominic]

6. Downsides

6.劣势

When the grammar is getting more complex, it becomes harder to maintain.

当语法越来越复杂时,它就变得更难维护。

It can be seen in the presented example. It’d be reasonably easy to add another expression, like Limit, yet it won’t be too easy to maintain if we’d decide to keep extending it with all other expressions.

在所介绍的例子中可以看到这一点。增加另一个表达式,比如Limit,是相当容易的,但是如果我们决定用所有其他表达式继续扩展它,那么维护它就不会太容易。

7. Conclusion

7.结语

The interpreter design pattern is great for relatively simple grammar interpretation, which doesn’t need to evolve and extend much.

解释器设计模式对于相对简单的语法解释来说是很好的,它不需要太多的进化和扩展。

In the example above, we showed that it is possible to build a SQL-like query in an object-oriented way with the help of the interpreter pattern.

在上面的例子中,我们展示了在解释器模式的帮助下,有可能以面向对象的方式建立一个类似SQL的查询。

Finally, you can find this pattern usage in JDK, particularly, in java.util.Patternjava.text.Format or java.text.Normalizer.

最后,你可以在JDK中找到这种模式的用法,特别是在java.util.Patternjava.text.Formatjava.text.Normalizer

As usual, the complete code is available on the Github project.

像往常一样,完整的代码可以在Github项目上获得。