Interpreterパターンの例
GoFの『オブジェクト指向における再利用のためのデザインパターン』では、Interpreterパターンは基本的にCompositeパターンと同じもので、その目的が違うだけだと紹介されています。Compositeパターンから結果として得られる階層構造が文法のようなものである場合、それはInterpreterパターンであると言えます。実際のところ、Compositeパターンのときに作成した例は、Interpreterパターンの実装例としても使えます。そういうわけで、本稿ではこれ以上の説明は必要なさそうです。
しかし、ここで終わるわけにもいきません。というのは、開発者の皆さんにInterpreterパターンを理解してもらうのに役立つ、ちょうど良い例がほとんど紹介されていないからです。本稿の例は、完璧な例ではないかもしれませんが、「interpreter design pattern」と検索して見つけた例と比較すると、かなり分かりやすいのではないでしょうか。
今回は、単純なブール言語をサポートするインタープリタの作成を目標にします。この言語は最終的に、動的な条件に基づいてフォルダ間で文書を移動させるための、簡単なコマンドセットの基礎となります。アプリケーションの例として「履歴書フィルタ」を考えてみます。例えば、次のようなコマンドがあるとしましょう。
move from incomingResumes to phoneScreen when contains fortran or smalltalk and olderThan 01/01/2006
ここで、式の2行目、「contains」で始まる行に着目します。Interpreterパターンは、式から結果が1つだけ得られるようなものに最も適しているからです。
今回の例では文書を扱うので、インタープリタの使い方を説明するために簡単な文書クラスが必要です。リスト1に、TextDocument
クラスの実装を示します。このクラスではcontains
メソッドをサポートし、作成日をカプセル化します。私はいつもテスト駆動開発をするようにしているので、TextDocument
クラスのテストも用意しました(リスト2)。
// Document.java public interface Document { public boolean contains(String... keywords); public java.util.Date getDate(); } // TextDocument.java import java.util.*; public class TextDocument implements Document { private final String contents; private final Date date; public TextDocument(Date date, String contents) { this.date = date; this.contents = contents; } @Override public boolean contains(String... textElements) { for (String text: textElements) if (contents.contains(text)) return true; return false; } @Override public Date getDate() { return date; } }
import static org.junit.Assert.*; import java.util.*; import org.junit.*; public class ContainsTest { private static final String CONTENTS = "these are the contents"; private TextDocument document; @Before public void createDocument() { document = new TextDocument(new Date(), CONTENTS); } @Test public void failsWhenTextNotInContents() { Expression expression = new Contains(CONTENTS + "x"); assertFalse(expression.evaluate(document)); } @Test public void passesWhenTextInContents() { String text = "contents"; assertTrue(CONTENTS.indexOf(text) != -1); Expression expression = new Contains(text); assertTrue(expression.evaluate(document)); } }
Interpreterパターンを使用してインタープリタを作成するための基本的な方法は、必要な文法を一連のクラスへと変換することです。1個のクラスは文法の各規則を表し、そのクラスのフィールドは規則の変換先のシンボルを表します。例えば、今回のサブ文法には次のような規則があります。
And ::= Expression 'and' Expression
つまり、今回のInterpreterパターンの実装には、2つのフィールドを持つAndという名前のクラスがあり、それぞれのフィールドにはExpression
オブジェクトが1つずつ格納されます(私はいつも、「and」などのテキストを表す独立したオブジェクトを作成する代わりに、それを今回のAndのような規則クラスのコードに直接組み込むようにしています)。