CodeZine(コードジン)

特集ページ一覧

デザインパターンの使い方: Interpreter

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/12/02 14:00

目次

 「and」式の新しいテストをリスト17に示します。現時点では、このテストは失敗します。

リスト17 非終端式のサポートのテスト
@Test
public void andExpression() {
   Expression expression =
      parser.parse("olderThan 03/31/2008 and contains java");
   And and = (And)expression;
   OlderThan olderThan = (OlderThan)and.getLeft();
   assertDate(2008, Calendar.MARCH, 31, olderThan.getDate());

   Contains contains = (Contains)and.getRight();
   assertKeywords((Contains)contains, "java");
}

 このテストに合格するパーサーを実装するには、アルゴリズムを少し見直す必要があります。ただ、最低限必要なコンポーネントは作ってあるので、それほど時間はかかりません(リスト18)。

リスト18 非終端式のサポート
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("olderThan", OlderThan.class);
      expressionTypes.put("and", And.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();
            current = createExpression(token);
            if (isProcessingBinaryExpression()) {
               And and = (And)pop();
               Expression left = pop();
               and.set(left, current);
               push(and);
            } else
               push(current);
         } else
            arguments.add(token);

      storeArguments();
      return pop();
   }

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

 もう一息です。当然、「and」に加えて「or」のサポートについてもテストし、さらにもっと複雑な式の場合でも正しく動作することを確認するための大きなテストをいくつか実行する必要があります。Parserクラスの最終バージョンをリスト19に示します。また、リファクタリングしたExpression実装クラスの階層の一部も示します。

リスト19 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);
   }
}

 全体的に見れば、なかなかの出来です。ソリューション全体の試作には満足できましたし、できあがったクラスでも、解析のときについてまわる複雑さを、ある程度切り離すことに成功しています。確かに、足りない点も多々あります(特にエラー処理)。しかし、残った点についても、かなり簡単な方法で試行してみることができると思います。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:デザインパターンの使い方

もっと読む

著者プロフィール

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

  • Jeff Langr(Jeff Langr)

    本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle...

あなたにオススメ

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5