はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、振る舞いに関するパターンの中のInterpreter(インタープリター)パターンを解説します。
23パターンの中でも、実務での登場頻度は比較的低いものの、「文法規則をクラス構造で表現する」という考え方は、プログラミング言語処理系や設定ファイルのパーサーを理解する上で役立ちます。
解決したい課題
特定の文法規則(ミニ言語)に従った文章を解釈し、実行(評価)したいという場面があります。
例えば、「A AND B」「A OR (B AND C)」のような、簡易的な論理式を評価するミニ言語を実装したいとします。
こうした文法を解釈する処理を、条件分岐や文字列処理だけで直接書こうとすると、文法が複雑になるにつれてコードも急速に複雑化し、保守が困難になります。
Interpreterパターンは、文法の規則(構文要素)ごとに、対応するクラスを用意し、これらを組み合わせて構文木(構造)を作ることで、文章の解釈を「オブジェクトの評価」として表現することで、この問題を解決します。
クラス構成
- AbstractExpression(抽象式): 解釈・評価を行うメソッド(
interpret())を持つ共通インターフェース - TerminalExpression(終端式): それ以上分解できない、文法の末端要素(変数、リテラル値など)を表すクラス
- NonterminalExpression(非終端式): 複数のExpressionを組み合わせた、複合的な文法規則(AND、OR等の演算子)を表すクラス
- Context(コンテキスト): 解釈に必要な、変数の値などの情報を保持するクラス
Javaでの実装例
先ほどの、簡易論理式(AND/OR)を評価するミニ言語を実装してみます。
Context:変数の値を保持するクラス
import java.util.HashMap;
import java.util.Map;
public class Context {
private final Map<String, Boolean> variables = new HashMap<>();
public void setVariable(String name, boolean value) {
variables.put(name, value);
}
public boolean getVariable(String name) {
return variables.getOrDefault(name, false);
}
}
AbstractExpression:共通インターフェース
public interface Expression {
boolean interpret(Context context);
}
TerminalExpression:変数を表す終端式
public class VariableExpression implements Expression {
private final String name;
public VariableExpression(String name) {
this.name = name;
}
@Override
public boolean interpret(Context context) {
return context.getVariable(name);
}
}
NonterminalExpression:AND・OR・NOTを表す非終端式
public class AndExpression implements Expression {
private final Expression left;
private final Expression right;
public AndExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean interpret(Context context) {
return left.interpret(context) && right.interpret(context);
}
}
public class OrExpression implements Expression {
private final Expression left;
private final Expression right;
public OrExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean interpret(Context context) {
return left.interpret(context) || right.interpret(context);
}
}
public class NotExpression implements Expression {
private final Expression expression;
public NotExpression(Expression expression) {
this.expression = expression;
}
@Override
public boolean interpret(Context context) {
return !expression.interpret(context);
}
}
利用側のコード:構文木を組み立てて評価する
public class Main {
public static void main(String[] args) {
Context context = new Context();
context.setVariable("A", true);
context.setVariable("B", false);
context.setVariable("C", true);
// 「A AND (B OR C)」という論理式を、構文木として組み立てる
Expression expression = new AndExpression(
new VariableExpression("A"),
new OrExpression(
new VariableExpression("B"),
new VariableExpression("C")
)
);
boolean result = expression.interpret(context);
System.out.println("A AND (B OR C) = " + result);
// 「NOT B AND A」を評価する例
Expression expression2 = new AndExpression(
new NotExpression(new VariableExpression("B")),
new VariableExpression("A")
);
System.out.println("NOT B AND A = " + expression2.interpret(context));
}
}
実行結果:
A AND (B OR C) = true
NOT B AND A = true
AndExpression・OrExpression・NotExpression・VariableExpressionという4種類のクラスの組み合わせだけで、任意の複雑さの論理式を表現できます。
「A AND (B OR C)」という式は、AndExpressionの中にOrExpressionが入れ子になった、木構造(構文木)として表現されており、interpret()を呼ぶと、この木構造を再帰的に辿りながら評価されます。
新しい演算子(例えばXOR)を追加したい場合も、新しいExpression実装クラスを1つ追加するだけで対応できます。
Compositeパターンとの関係
Interpreterパターンの構文木の構造は、以前解説したCompositeパターンとよく似ています。
実際、AndExpressionやOrExpressionは、複数の子Expressionを持つという点で、Compositeパターンにおける「Composite(枝)」の役割を、VariableExpressionは「Leaf(葉)」の役割を果たしていると捉えることもできます。
使用場面
- 簡易的な設定ファイル・クエリ言語・ルールエンジン(条件式の評価など)を自作したい場合
- SQL文やロジック式のような、限定的な文法を持つ言語の解釈処理
- 正規表現エンジン、数式パーサーなど、構文解析が必要な処理
注意点
Interpreterパターンは、文法規則が複雑になるほどクラス数が増え、パフォーマンスも低下しやすい傾向があります。
実務で本格的な言語処理系が必要な場合は、このパターンを自前で実装するのではなく、ANTLRのような専用のパーサジェネレータや、既存の式評価ライブラリを使うことが一般的です。
GoFの23パターンの中では、実務での直接的な使用頻度が比較的低いパターンとされています。
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | 特定の文法規則に従った文を、コードとして解釈・評価したい |
| 実現方法 | 文法の各要素をクラスとして表現し、組み合わせた構文木として文を表現する |
| メリット | 新しい文法要素の追加が、新しいクラスの追加だけで対応できる |
| 注意点 | 複雑な文法には不向き。実務では専用のパーサジェネレータが使われることが多い |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


