SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

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

  • X ポスト
  • このエントリーをはてなブックマークに追加

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

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

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
デザインパターンの使い方連載記事一覧

もっと読む

この記事の著者

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

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Jeff Langr(Jeff Langr)

本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle Bob Martin著、Prentice Hall、2008年8月)にも寄稿している。また、ソフトウェア開発に関する記事を80件以上執筆しており、そのうちの...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/3291 2008/12/02 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング