Javaのインタプリタデザインパターン
1.概要
このチュートリアルでは、行動的なGoFデザインパターンの1つであるインタプリタを紹介します。
最初に、その目的の概要と解決しようとしている問題について説明します。
それでは、インタプリタのUML図と実際の例の実装を見ていきます。
2.通訳者デザインパターン
簡単に言うと、パターンは特定の言語の文法をインタプリタ自身が評価できるオブジェクト指向の方法で定義します。
それを念頭に置いて、技術的にはカスタムの正規表現、カスタムのDSLインタプリタ、または任意の人間の言語を解析し、抽象構文木を構築してから解釈を実行することができます。
これらは潜在的なユースケースのほんの一部にすぎませんが、しばらく考えてみると、たとえばIDEで、もっと多くの用途が見つかる可能性があります。貴重なヒント。
通常、文法が比較的単純な場合はインタプリタパターンを使用します。
さもなければ、それは維持するのが難しくなるかもしれません。
3. UML図
リンク:/uploads/Interpreter-100×57.png%20100w[]
上の図は、
Context
と
Expression
の2つの主要なエンティティを示しています。
今、どんな言語でも何らかの方法で表現される必要があります、そして、言葉(表現)は与えられた文脈に基づいてある意味を持つようになるでしょう。
__AbstractExpression
__は、コンテキストをパラメータとしてとる1つの抽象メソッドを定義します。そのおかげで、
それぞれの式は文脈
に影響を与え、その状態を変更し、解釈を続けるか、結果そのものを返します。
そのため、コンテキストはグローバルな処理状態の保持者となり、解釈プロセス全体で再利用されることになります。
では、
TerminalExpression
と
NonTerminalExpression
の違いは何ですか?
NonTerminalExpression
には、1つ以上の他の
AbstractExpressions
が関連付けられている可能性があるため、再帰的に解釈することができます。最後に、解釈の過程は結果を返すTerminalExpressionで終わる必要があります。
NonTerminalExpression
は**
composite
です。
最後に、クライアントの役割は、すでに作成された
抽象構文木
を作成または使用することです。
4.実装
実際のパターンを示すために、単純なSQLのような構文をオブジェクト指向の方法で構築します。それが解釈されて結果が返されます。
まず、
Select、From、
、および
Where
式を定義し、クライアントのクラスに構文ツリーを構築して、解釈を実行します。
Expression
インターフェースは、メソッドを解釈します:
List<String> interpret(Context ctx);
次に、最初の式
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);
}
}
選択される列名と、コンストラクタのパラメータとして
From
型の別の具象__式を取得します。
オーバーライドされた__interpret()メソッドでは、コンテキストの状態を設定し、その解釈をコンテキストとともに別の式に渡します。
このように、それは__NonTerminalExpressionであることがわかります。
別の式は
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);
}
}
現在のSQLでは、where句はオプションです。したがって、このクラスは端末式または非端末式のいずれかです。
ユーザーがwhere句を使用しないことにした場合、
From
式は__ctx.search()呼び出しで終了し、結果が返されます。それ以外の場合は、さらに解釈されます。
__Where
__式は、必要なフィルタを設定してコンテキストを修正し、検索呼び出しで解釈を終了します。
class Where implements Expression {
private Predicate<String> filter;
//constructor
@Override
public List<String> interpret(Context ctx) {
ctx.setFilter(filter);
return ctx.search();
}
}
例えば、
__Context
__classはデータベーステーブルを模したデータを保持します。
Expression
の各サブクラスと検索方法によって変更される3つのキーフィールドがあります。
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
__クラスを見てみましょう。
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);
}
}
まず、作成した式を使用して構文ツリーを作成し、コンテキストを初期化してから解釈を実行します。コンテキストは再利用されますが、上で示したように、各検索呼び出しの後に自動的に消去されます。
プログラムを実行すると、出力は次のようになります。
----[John, Jan, Dominic][John Doe, Jan Kowalski, Dominic Doom][Dominic]----
6.欠点
文法が複雑になってくると、維持が難しくなります。
提示された例でそれを見ることができます。
Limit
のような別の式を追加するのはかなり簡単ですが、他のすべての式でそれを拡張し続けることにした場合、保守するのは簡単ではありません。
7.まとめ
インタプリタのデザインパターンは、
比較的単純な文法解釈
には非常に優れています。
上記の例では、インタプリタパターンを利用してオブジェクト指向の方法でSQLのようなクエリを構築することが可能であることを示しました。
最後に、このパターンの使い方はJDK、特に
java.util.Pattern
、
java.text.Format
、または
java.text.Normalizer
にあります。
いつものように、完全なコードはhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns[Githubプロジェクト]にあります。