最低限の準備が整ったので、Documentオブジェクトを操作する簡単な規則クラスの作成に取りかかります。例えば、Containsクラス(リスト3)は、渡されたキーワードのいずれかが文書に含まれているかどうかを表します。OlderThanクラス(リスト4)は、指定した日付より文書が古いかどうかを判定します。クライアント側から条件を評価できるようにするために、各クラスではExpressionインターフェース(リスト5)を実装しておきます。
public class Contains implements Expression {
private final String[] keywords;
public Contains(String... keywords) {
this.keywords = keywords;
}
@Override
public boolean evaluate(Document document) {
return document.contains(keywords);
}
}
import java.util.*;
public class OlderThan implements Expression {
private final Date date;
public OlderThan(Date date) {
this.date = date;
}
@Override
public boolean evaluate(Document document) {
return document.getDate().before(date);
}
}
public interface Expression {
boolean evaluate(Document document);
}
Interpreterパターンでは、ContainsやOlderThanのような式のことを「終端式(Terminal Expressions)」と呼びます。終端式は、複合式(「and」や「or」を使用した式)を作るときに基本となる構成要素です。リスト6にAndクラスのテスト、リスト7にAndクラスの実装を示します。
Andクラスにあるevaluateメソッドのコードが、CompositeパターンとInterpreterパターンで最も重要な部分です。「and」式を評価することで、この式の左側と右側の式がそれぞれ再帰的に評価されます。Interpreterパターンでは、Andのような複合式のことを「非終端式(Nonterminal Expressions)」と呼びます。
import static org.junit.Assert.*;
import java.util.*;
import org.junit.*;
public class AndTest {
private static final String CONTENTS = "these are the contents";
private static final Date NOW = new Date();
private TextDocument document;
@Before
public void createDocument() {
document = new TextDocument(NOW, CONTENTS);
}
@Test
public void failsWhenNeitherConditionMet() {
Expression expression = new And(new Contains(CONTENTS + "x"),
new OlderThan(NOW));
assertFalse(expression.evaluate(document));
}
@Test
public void failsWhenOnlyFirstConditionMet() {
String text = "contents";
assertTrue(CONTENTS.indexOf(text) != -1);
Expression expression = new And(new Contains(text),
new OlderThan(NOW));
assertFalse(expression.evaluate(document));
}
@Test
public void passesWhenBothConditionsMet() {
String text = "contents";
assertTrue(CONTENTS.indexOf(text) != -1);
Date future = new Date(NOW.getTime() + 1);
Expression expression = new And(new Contains(text),
new OlderThan(future));
assertTrue(expression.evaluate(document));
}
}
public class And implements Expression {
private final Expression leftExpression;
private final Expression rightExpression;
public And(Expression leftExpression,
Expression rightExpression) {
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
public boolean evaluate(Document document) {
return leftExpression.evaluate(document) &&
rightExpression.evaluate(document);
}
}
Interpreterパターンでは、ExpressionオブジェクトからExpressionオブジェクトに(evaluateメソッド経由で)渡されるDocumentのことを、コンテキストと呼びます。
Interpreterパターンには多くのメリットがあります。既に見てきたように、個々の式クラスのテストは小さくて分かりやすいですし、とてもすっきりしています。新しい式クラスのセットを文法に組み込むことも簡単で、他の既存の式に影響することはありません。
Interpreterパターンは、大規模な文法を扱うには不向きです。規模が大きい場合はパーサージェネレータなどの利用を検討すべきです。文法が複雑になると、Interpreterパターンではパフォーマンスに問題が発生したり、使い勝手が著しく落ちたりする可能性があります。
