Java : Interpreter パターン (図解/デザインパターン)
Interpreter パターンとは、GoF によって定義されたデザインパターンの1つです。
独自のドメイン固有言語(DSL) を実装するための設計です。
本記事では、Interpreter パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
- 振る舞いに関するパターン
概要
Interpreter パターンとは、
- ある分野に特化したコンピュータ言語
を実装するための設計です。
「ある分野に特化したコンピュータ言語」のことを
とも言います。
例えば、データベースの問い合わせに特化した SQL などです。
Interpreter は、日本語的に発音すると「インタープリター」もしくは「インタプリタ」となります。
意味は「解釈する人」ですね。
【Interpreter パターンのイメージ】
次のような テキスト(文字列) があるとします。
x * ( y + z )
変数 x, y, z を使った「計算式」ですね。
このテキストを入力して、答えを返すプログラムを考えてみましょう。
テキストのままだと「式」を計算できません。
そこで、構文解析器でテキストを解析します。
一般的には次のような 木構造 が得られます。
この木構造自体は Composite パターン で実現できます。
そして、Interpreter パターンでは、この木構造を「式」として 解釈(Interpret) します。
そのときに「コンテキスト」オブジェクトをパラメータとして使います。
これが Interpreter パターンのざっくりとしたイメージになります。
Composite パターン の応用ともいえるので、そちらの記事も、ぜひ合わせてご確認ください。
関連記事:
クラス図
上の図は、Interpreter パターンの一般的なクラス図です。
それでは、このクラス図をそのままコードにしてみましょう。
まずは、木構造を表す AbstractExpression インタフェースです。
Expression は「式」という意味になります。
public interface AbstractExpression {
void interpret(Context context);
}
次に、AbstractExpression インタフェースの実装クラスです。
- TerminalExpression : 子ノードを持たない
- NonTerminalExpression : 子ノードを持つ
public class TerminalExpression implements AbstractExpression {
private final String value;
public TerminalExpression(String value) {
this.value = value;
}
@Override
public void interpret(Context context) {
context.append(value);
}
}
public class NonTerminalExpression implements AbstractExpression {
private final AbstractExpression exp1;
private final AbstractExpression exp2;
public NonTerminalExpression(AbstractExpression exp1, AbstractExpression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public void interpret(Context context) {
context.append("( ");
exp1.interpret(context);
context.append(", ");
exp2.interpret(context);
context.append(" )");
}
}
最後に Context クラスです。
Interpreter パターンにおける Context クラスに決まった形はありません。
今回は、文字列を追加する append メソッドを定義しています。
public class Context {
private final StringBuilder sb = new StringBuilder();
public void append(String value) {
sb.append(value);
}
@Override
public String toString() {
return sb.toString();
}
}
それでは、これらのクラスを使ってみましょう。
final var exp = new NonTerminalExpression(
new TerminalExpression("X"),
new NonTerminalExpression(
new TerminalExpression("Y"),
new TerminalExpression("Z")
)
);
final var context = new Context();
exp.interpret(context);
System.out.println(context); // ( X, ( Y, Z ) )
結果も問題なしですね。
木構造を解釈(Interpret) して、結果となる文字列が出力できました。
具体的な例
もう少し具体的な例も見てみましょう。
Interpreter パターンのイメージ のところでも紹介した
x * ( y + z )
の「式」を考えてみます。
まずは木構造の表す Exp インタフェースです。
(Exp は Expression の省略形です)
interpret メソッドは、戻り値として計算結果を返すようにしています。
public interface Exp {
int interpret(Context context);
}
次に Exp インタフェースの実装クラスです。
- VariableExp : 変数
- AddExp : 足し算
- MultiplyExp : 掛け算
public class VariableExp implements Exp {
private final String name;
public VariableExp(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
return context.getVariable(name);
}
public String getName() {
return name;
}
}
public class AddExp implements Exp {
private final Exp exp1;
private final Exp exp2;
public AddExp(Exp exp1, Exp exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public int interpret(Context context) {
return exp1.interpret(context) + exp2.interpret(context);
}
}
public class MultiplyExp implements Exp {
private final Exp exp1;
private final Exp exp2;
public MultiplyExp(Exp exp1, Exp exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public int interpret(Context context) {
return exp1.interpret(context) * exp2.interpret(context);
}
}
最後に Parser クラスです。
「式」である
x * ( y + z )
を構文解析して Exp オブジェクトを生成します。
ただし、構文解析の処理は簡略化 … というか省略しています。
構文解析は複雑、かつ、Interpreter パターンの本質の部分ではないためです。
public class Parser {
public static Exp parse(String text) {
// 注意 : x * (y + z) の「式」を直接作成
return new MultiplyExp(
new VariableExp("x"),
new AddExp(
new VariableExp("y"),
new VariableExp("z")
)
);
}
}
それでは、これらのクラスを使ってみましょう。
final var text = "x * (y + z)";
final var exp = Parser.parse(text);
final var context = new Context();
// ----------------
// 2 * (3 + 4) = 14
context.putVariable("x", 2);
context.putVariable("y", 3);
context.putVariable("z", 4);
final var ret1 = exp.interpret(context);
System.out.println(ret1); // 14
// ----------------
// 3 * (4 + 5) = 27
context.putVariable("x", 3);
context.putVariable("y", 4);
context.putVariable("z", 5);
final var ret2 = exp.interpret(context);
System.out.println(ret2); // 27
結果も問題なしですね。
無事に
x * ( y + z )
が計算できました。
まとめ
Interpreter パターンとは、
- ある分野に特化したコンピュータ言語
を実装するための設計です。
もし、独自の ドメイン固有言語(DSL) が必要になったら、検討してみるのもよいかもしれません。
関連記事
- 標準APIにならう命名規則
- コメントが少なくて済むコードを目指そう
- シングルトン・パターンの乱用はやめよう
- メソッドのパラメータ(引数)は使う側でチェックしよう
- 不変オブジェクト(イミュータブル) とは
- 依存性の注入(DI)をもっと気軽に
- 不要になったコードはコメントアウトで残さずに削除しよう
- 簡易的な Builder パターン
- 読み取り専用(const) のインタフェースを作る
- 図解/デザインパターン一覧 (GoF)
- Abstract Factory パターン
- Adapter パターン
- Bridge パターン
- Builder パターン
- Chain of Responsibility パターン
- Command パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Factory Method パターン
- Flyweight パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- Prototype パターン
- Proxy パターン
- Singleton パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン