Interpreterパターン


 ★☆☆

Interpreterパターン

何らかの文法規則で解析された結果を利用する

Interpreter とは、英語で「解釈者・説明者」を意味する単語です。 何らかのフォーマットで書かれたファイルの中身を、解析した結果にそって何らかの処理を行いたい場合があります。 Interpreter パターンとは、このような「解析した結果」得られた手順に則った処理を実現するために最適なパターンです。分かりにくいですが、解析する方法ではなく、解析した結果を利用するパターンです。


そのフォーマットとは、htmlやxml、プロパティファイルのような静的なものもあれば、ソースファイルのように処理手順を記述したものもあります。SQL、シェル、バッチ、マクロ、XSLT等もあります。通常、構文解析という操作で、文法規則にのっとって解析が行なわれ、解析結果はツリー構造のオブジェクトとして保存されます。全体は部分から構成され、部分はさらに小さな部分から構成されます。

全体に対して何らかのメソッドを適用すると、部分に対しても順次再帰的に同じメソッドを呼び出していきます。基本的に、Interpreter はComposite と同じものです。

ただ通常、Composite や Interpreter を適用するパターンは多くありません。再帰的に処理するのが便利なのは、無限に階層が深くなる可能性がある場合で、一覧表示や計算や処理を行う場合です。容器の中に容器を入れられるようにすれば、再帰構造ができあがりますが、設定ファイルなどで用いる場合はかえって混乱を招きます。無限に階層が深くなっても、利用するアプリケーション側で対応する必要が出てくるので、有限の構造にした方がいいこともあります。


例題

四則演算をします。演算の手順はすでに得られています。

  1 + 4 * 2

  (1 + 4) * 2



実行結果
9.0           
10.0








Interpreterパターンを使用しない例

Calculator.java
public class Calculator { private double result = 0.0; public double calculate(Stack<String> operand) { if (operand.empty()) return result; String operator = (String)operand.pop(); String operand2 = (String)operand.pop(); double op2; if (operand2.equals("+") || operand2.equals("*")) { operand.push(operand2); op2 = calculate(operand); } else { op2 = Double.parseDouble(operand2); } String operand1 = (String)operand.pop(); double op1; if (operand1.equals("+") || operand1.equals("*")) { operand.push(operand1); op1 = calculate(operand); } else { op1 = Double.parseDouble(operand1); } if (operator.equals("+")) { result = op1 + op2; } else if (operator.equals("*")) { result = op1 * op2; } return result; } }
Main.java
public class Main { public static void main(String[] args) { Stack<String> operand = new Stack<String>(); // 1 + 4 * 2 operand.push("1"); operand.push("4"); operand.push("2"); operand.push("*"); operand.push("+"); Calculator cal = new Calculator(); System.out.println(cal.calculate(operand)); // (1 + 4) * 2 operand.clear(); operand.push("1"); operand.push("4"); operand.push("+"); operand.push("2"); operand.push("*"); System.out.println(cal.calculate(operand)); } }

Interpreterパターンを使用した例

IOperand.java
public interface IOperand { double getOperand(); }
Ingredient.java
public class Ingredient implements IOperand { private double operand = 0; public Ingredient(double operand) { this.operand = operand; } public double getOperand() { return this.operand; } }
Expression.java
public class Expression implements IOperand { private IOperator operator = null; public Expression(IOperator operator) { this.operator = operator; } public double getOperand() { return operator.execute().getOperand(); } }
IOperator.java
public interface IOperator { public IOperand execute(); }
OperationAdd.java
public class OperationAdd implements IOperator { private IOperand operand1 = null; private IOperand operand2 = null; public OperationAdd(IOperand operand1, IOperand operand2) { this.operand1 = operand1; this.operand2 = operand2; } public IOperand execute() { return new Ingredient(operand1.getOperand() + operand2.getOperand()); } }
OperationMultiply.java
public class OperationMultiply implements IOperator { private IOperand operand1 = null; private IOperand operand2 = null; public OperationMultiply(IOperand operand1, IOperand operand2) { this.operand1 = operand1; this.operand2 = operand2; } public IOperand execute() { return new Ingredient(operand1.getOperand() * operand2.getOperand()); } }
Main.java
public class Main { public static void main(String[] args) { Ingredient in1 = new Ingredient(1); Ingredient in2 = new Ingredient(4); Ingredient in3 = new Ingredient(2); // 1 + 4 * 2 Expression ex1 = new Expression(new OperationMultiply(in2, in3)); Expression ex2 = new Expression(new OperationAdd(in1, ex1)); System.out.println(ex2.getOperand()); // (1 + 4) * 2 ex1 = new Expression(new OperationAdd(in1, in2)); ex2 = new Expression(new OperationMultiply(ex1, in3)); System.out.println(ex2.getOperand()); } }

この例では、演算式を解析し、逆ポーランド記法で書きなおされています。これを計算して答えを得ます。

逆ポーランド記法

  1 + 4 * 2 → 1 4 2 * +

 (1 + 4) * 2 → 1 4 + 2 *

取り出したものが演算子だったら、スタックから次の2つを取り出して演算します。しかし、取り出した中にまた演算子があったら、それは式なので、またスタックから次の2つを取り出して...というように繰り返します。ここでは、再起呼び出しを利用しています。計算はうまくできているようです。

しかし、この後減算と除算を追加しようとしたら、calculate() に新たに減算と除算のための if 文を追加しなければなりません。これでは拡張性が良いとは言えません。

Interpreter パターンでは、項が数値でも式でも同じようにオブジェクトとして扱っています。

例えば、1 + 4 * 2 は、

   +
   /\
  1  *
     /\
    4  2
というようにオブジェクトを関連付けています。


まず、4*2 を ex1 という Expression インスタンスに作成します。operator は OperationMultiply です。

次に、1+ex1 を ex2 という Expression インスタンスに作成します。operator は OperationAdd です。

そして、計算結果を求めるために、main から ex2 の getOperand() を呼び出します。どのような演算をするかは、ex2 が知っています(Expression の operator)。OperationAdd ならば加算ですし、OperationMultiply ならば乗算です。

ex2 の持つ IOperator は OperationAdd です。OperationAdd の excute() を呼んで、1+ex1 を計算しようとしますが、ex1 が式なのでさらにその値を計算(getOperand())します。このとき、getOperand() は、項(1 とか ex1)が式(Expression)の場合は、演算をして(execute)から、その結果を返しています。また、数値(Ingredient)の場合は、そのまま値を返しています。

ex1 は式なので、Expression インスタンスの getOperand() が呼び出されます。ex1 の持つ IOperator は OperationMultiply です。OperationMultiply の excute() を呼んで、4*2 が計算され 8 が戻り値となります。

その結果、1+ex1 が 1+8 となり、計算され 9 が戻り値となり、表示されます。


Interpreter パターンであれば、この後、減算と除算を追加する場合でも、IOperator を継承して作成するだけですみます。