Java解释器设计模式

Interpreter Design Pattern in Java

1.概述

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

首先,我们将概述其目的并解释其尝试解决的问题。

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

2.口译员设计模式

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

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

这些只是一些潜在的用例,但是如果我们考虑一会儿,我们会发现它的更多用法,例如在我们的IDE中,因为它们不断解释我们正在编写的代码,从而为我们提供了 无价的提示。

语法比较简单时,通常应使用解释器模式。

否则,可能难以维护。

3. UML图

 width=

上图显示了两个主要实体:上下文和表达式。

现在,任何语言都需要以某种方式表达,并且单词(表达式)将根据给定的上下文具有某种含义。

AbstractExpression定义了一种将上下文作为参数的抽象方法。 因此,每个表达式都会影响上下文,更改其状态并继续进行解释或返回结果本身。

因此,上下文将成为全局处理状态的所有者,并将在整个解释过程中重用它。

那么,TerminalExpression和NonTerminalExpression有什么区别?

NonTerminalExpression可能与一个或多个其他AbstractExpression相关联,因此可以递归解释。 最后,解释的过程必须以TerminalExpression完成,后者将返回结果。

值得注意的是NonTerminalExpression是复合的。

最后,客户端的作用是创建或使用已经创建的抽象语法树,它仅是用所创建的语言定义的句子。

4.实施

为了显示实际的模式,我们将以面向对象的方式构建一个简单的类似于SQL的语法,然后将其解释并返回结果。

首先,我们将定义Select,From和Whereexpressions,在客户端的类中构建语法树并运行解释。

TheExpression接口将具有解释方法:

1
List<String> interpret(Context ctx);

接下来,我们定义第一个表达式theSelect类:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
    }
}

它获取要选择的列名称以及构造函数中Fromas参数的另一个具体的ExpressionExpression类型。

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

这样,我们看到它是一个NonTerminalExpression。

另一个表达式是From类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
    }
}

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

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

Whereexpression再次通过设置必要的过滤器来修改上下文,并通过搜索调用终止解释:

1
2
3
4
5
6
7
8
9
10
11
12
class Where implements Expression {

    private Predicate<String> filter;

    // constructor

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

对于该示例,Contextclass包含模拟数据库表的数据。

请注意,它具有三个关键字段,这些字段分别由Expression的每个子类和search方法修改:

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
26
27
28
29
30
31
32
33
34
35
36
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;
    }
}

搜索完成后,上下文将自动清除,因此列,表和过滤器将设置为默认值。

这样,每种解释都不会影响彼此。

5.测试

出于测试目的,让我们看一下InterpreterDemo类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
    }
}

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

通过运行程序,输出应如下所示:

1
2
3
[John, Jan, Dominic]
[John Doe, Jan Kowalski, Dominic Doom]
[Dominic]

6.缺点

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

在给出的例子中可以看出。 添加另一个表达式,例如Limit,相当容易,但是如果我们决定继续使用所有其他表达式扩展它,则维护起来并不容易。

7.结论

解释器设计模式非常适合于相对简单的语法解释,不需要进行太多的扩展。

在上面的示例中,我们表明可以借助解释器模式以面向对象的方式构建类似SQL的查询。

最后,您可以在JDK中找到这种模式的用法,特别是在java.util.Pattern,java.text.Format或java.text.Normalizer中。

和往常一样,完整的代码可在Github项目上找到。