October 24, 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 »

Getting close! I obviously need tests to drive out support for "or" in addition to "and," and then a few big tests to demonstrate that this truly works for more involved expressions. A final version of the parser class appears in Listing 19. I also show some of the refactored Expression class hierarchy.

Listing 19: The Parser.

// Expression.java
import java.util.*;

public interface Expression {
   void setArgs(List<String> args);
   boolean evaluate(Document document);
}

// BinaryExpression
import java.util.*;

abstract public class BinaryExpression implements Expression {
   protected Expression leftExpression;
   protected Expression rightExpression;

   public void set(Expression leftExpression,
                   Expression rightExpression) {
      this.leftExpression = leftExpression;
      this.rightExpression = rightExpression;
   }

   abstract public boolean evaluate(Document document);

   public Expression getLeft() {
      return leftExpression;
   }

   public Expression getRight() {
      return rightExpression;
   }

   @Override
   public void setArgs(List<String> args) {
   // violation of Liskov! OK for now.
   }
}

// Or.java
public class Or extends BinaryExpression implements Expression {
   public boolean evaluate(Document document) {
      return leftExpression.evaluate(document) ||
             rightExpression.evaluate(document);
   }
}


// KeywordExpression
import java.util.*;

abstract public class KeywordExpression implements Expression {
   protected List<String> keywords;

   @Override
   abstract public boolean evaluate(Document document);

   @Override
   public void setArgs(List<String> keywords) {
      this.keywords = keywords;
   }

   String[] getKeywords() {
      return (String[])keywords.toArray(new String[0]);
   }
}

public class Contains extends KeywordExpression
   implements Expression {
   @Override
   public boolean evaluate(Document document) {
      return document.contains(ListUtil.asArray(keywords));
   }
}

// Parser.java
import java.util.*;

public class Parser {
   private List<String> arguments = new ArrayList<String>();
   private Map<String, Class<? extends Expression>>
      expressionTypes = new HashMap<String, Class<?
      extends Expression>>();
   {
      expressionTypes.put("contains", Contains.class);
      expressionTypes.put("excludes", Excludes.class);
      expressionTypes.put("olderThan", OlderThan.class);
      expressionTypes.put("and", And.class);
      expressionTypes.put("or", Or.class);
   }

   private Expression current;
   private List<Expression> expressions =
      new ArrayList<Expression>();

   public Expression parse(String expressionText) {
      String[] tokens = expressionText.split(" ");
      for (String token: tokens)
         if (isKeyword(token)) {
            storeArguments();
            newExpression(token);
         } else
            arguments.add(token);

      storeArguments();
      return pop();
   }

   private void newExpression(String token) {
      current = createExpression(token);
      if (isProcessingBinaryExpression()) {
         BinaryExpression binary = (BinaryExpression)pop();
         Expression left = pop();
         binary.set(left, current);
         push(binary);
      } else
         push(current);
   }

   private boolean isProcessingBinaryExpression() {
      return expressions.size() == 2;
   }

   private void storeArguments() {
      if (current == null)
         return;
      current.setArgs(arguments);
      arguments = new ArrayList<String>();
   }

   private boolean push(Expression expression) {
      return expressions.add(expression);
   }

   private Expression pop() {
      return expressions.remove(expressions.size() - 1);
   }

   private Expression createExpression(String command) {
      try {
         return expressionTypes.get(command).newInstance();
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }

   private boolean isKeyword(String token) {
      return expressionTypes.containsKey(token);
   }
}

Overall, not a bad way to go. I felt comfortable test driving the entire solution, and ended up with a class that manages to isolate some of the complexity that parsing requires. Obviously, there's a lot missing here, particularly error handling, but I'm confident that I could test drive the remainder of these in a fairly straightforward fashion.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff has contributed a couple chapters to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 75 articles on software development, with over thirty appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.





Page 6 of 6



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel