July 25, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working With Design Patterns: Interpreter

  • May 22, 2008
  • By Jeff Langr
  • Send Email »
  • More Articles »

Listing 3: The Contains expression.

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);
   }
}

Listing 4: The OlderThan expression.

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);
   }
}

Listing 5: The Expression interface.

public interface Expression {
   boolean evaluate(Document document);
}

The Contains and OlderThan expressions are known as terminal expressions in the interpreter pattern. They are the building blocks for composite expressions, such as expressions involving the use of "and" or "or." Listing 6 shows a test for an And class, and Listing 7 shows the implementation.

The code for evaluate in the And class presents the core of the composite/interpreter pattern: Evaluating an "and" expression is a matter of recursively evaluating each of its left-hand and right-hand expressions. Composites such as And are known as nonterminal expressions in the interpreter pattern.

Listing 6: A test for a binary nonterminal expression.

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));
   }
}

Listing 7: The And class.

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);
   }
}

Within the interpreter design pattern, the Document passed from Expression object to Expression object (via the evaluate method) is known as the context.

The interpreter pattern provides many benefits. As you can see, tests for individual expression classes are small and easy to understand. The tests, too, are very straightforward. It's also easy to incorporate a new set of expression classes into the grammar without impacting other, existing expressions.

Interpreter is not designed for large grammars; for that, you should consider parser generators and the like. It can suffer from performance problems, and can get too unwieldy with more complex grammars.





Page 2 of 6



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel