広告

Java : Interpreter パターン (図解/デザインパターン)

Interpreter パターンとは、GoF によって定義されたデザインパターンの1つです。
独自のドメイン固有言語(DSL) を実装するための設計です。

本記事では、Interpreter パターンを Java のコード付きで解説していきます。


デザインパターン(GoF) 関連記事

図解/デザインパターン一覧

概要

Interpreter パターンは、コンピュータプログラミングにおけるデザインパターンの一つである。Interpreter パターンの基本的な考えは、定義された種類の問題を素早く解くために、Compositパターンを使って ドメイン固有言語を実装することである。

Interpreter パターンとは、

  • ある分野に特化したコンピュータ言語

を実装するための設計です。

「ある分野に特化したコンピュータ言語」のことを

とも言います。
例えば、データベースの問い合わせに特化した SQL などです。

Interpreter は、日本語的に発音すると「インタープリター」もしくは「インタプリタ」となります。
意味は「解釈する人」ですね。


【Interpreter パターンのイメージ】

イメージ図1


次のような テキスト(文字列) があるとします。

x * ( y + z )

変数 x, y, z を使った「計算式」ですね。
このテキストを入力して、答えを返すプログラムを考えてみましょう。

テキストのままだと「式」を計算できません。

そこで、構文解析器でテキストを解析します。
一般的には次のような 木構造 が得られます。

イメージ図2

この木構造自体は Composite パターン で実現できます。

そして、Interpreter パターンでは、この木構造を「式」として 解釈(Interpret) します。
そのときに「コンテキスト」オブジェクトをパラメータとして使います。

イメージ図3

これが Interpreter パターンのざっくりとしたイメージになります。
Composite パターン の応用ともいえるので、そちらの記事も、ぜひ合わせてご確認ください。

関連記事:


クラス図

クラス図1

上の図は、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 )

の「式」を考えてみます。

クラス図2

まずは木構造の表す 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) が必要になったら、検討してみるのもよいかもしれません。


関連記事

ページの先頭へ