Interpreter Design Pattern in Java
1.概述
在本教程中,我们将介绍行为GoF设计模式之一-解释器。
首先,我们将概述其目的并解释其尝试解决的问题。
然后,我们将看一下Interpreter的UML图和实际示例的实现。
2.口译员设计模式
简而言之,模式以面向对象的方式定义特定语言的语法,该语言可以由解释器本身进行评估。
考虑到这一点,从技术上讲,我们可以构建自定义正则表达式,自定义DSL解释器,或者可以解析任何人类语言,构建抽象语法树,然后运行解释。
这些只是一些潜在的用例,但是如果我们考虑一会儿,我们会发现它的更多用法,例如在我们的IDE中,因为它们不断解释我们正在编写的代码,从而为我们提供了 无价的提示。
语法比较简单时,通常应使用解释器模式。
否则,可能难以维护。
3. UML图
上图显示了两个主要实体:上下文和表达式。
现在,任何语言都需要以某种方式表达,并且单词(表达式)将根据给定的上下文具有某种含义。
AbstractExpression定义了一种将上下文作为参数的抽象方法。 因此,每个表达式都会影响上下文,更改其状态并继续进行解释或返回结果本身。
因此,上下文将成为全局处理状态的所有者,并将在整个解释过程中重用它。
那么,TerminalExpression和NonTerminalExpression有什么区别?
NonTerminalExpression可能与一个或多个其他AbstractExpression相关联,因此可以递归解释。 最后,解释的过程必须以TerminalExpression完成,后者将返回结果。
值得注意的是NonTerminalExpression是复合的。
最后,客户端的作用是创建或使用已经创建的抽象语法树,它仅是用所创建的语言定义的句子。
4.实施
为了显示实际的模式,我们将以面向对象的方式构建一个简单的类似于SQL的语法,然后将其解释并返回结果。
首先,我们将定义Select,From和Whereexpressions,在客户端的类中构建语法树并运行解释。
TheExpression接口将具有解释方法:
1 |
接下来,我们定义第一个表达式theSelect类:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
它获取要选择的列名称以及构造函数中Fromas参数的另一个具体的ExpressionExpression类型。
请注意,在重写的interpret()方法中,它设置了上下文的状态,并将解释与上下文一起进一步传递给另一个表达式。
这样,我们看到它是一个NonTerminalExpression。
另一个表达式是From类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
现在,在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项目上找到。