Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

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

Template Methodでクラス間の重複をなくす

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

「Template Methodパターン」は、アルゴリズムの途中で必要な処理を抽象メソッドに委ね、その実装を変えることで処理が変えられるようにするデザインパターンです。本稿では、実際にリファクタリングを行う具体的な例を見ながら、「Template Methodパターン」の使用方法を説明していきます。

目次

Template Methodパターンの例

 デザインパターンは強力なツールです。強力なツールには常に誤用の可能性があります。注意を怠ると、ぐちゃぐちゃで何だかよくわからないものを作ってしまったりする危険性があります。

 Template Methodは、2つ以上の関連するクラスの間での重複をなくすための、1つの解決策を提供するデザインパターンです。これはGammaらによる『Design Patterns』(1995年)で取り上げられていた23個のパターンのうちの1つです。

 Template Methodパターンはリファクタリングの目標と考えるのがよいでしょう。本稿では、この目標に向けてコードを手直しする方法を、具体的な例を見ながら説明していきます。「Template Methodパターンとは何ぞや」ではなく、実際にリファクタリングを行い、その結果について語ることにします。

 今回のサンプルでは、スキャンしたフォームを管理するためのルールベースのワークフローシステムを構築します。最初に取り組むルールでは、フォーム上で指定されたステートが、引数として渡されるステートと一致していることが求められます。このルールを表すCheckStateRuleクラスでは、次のようなRuleインターフェースを実装します。

import java.util.*;

public interface Rule {
   boolean eval(Case c, List<String> val, boolean returnTrue);
}

 ワークフローシステムはルールを実行します。ルールを実行することにより、ワークフローの中でフォームを動かすことができます。

 CheckStateRuleクラスのコードは単純です。ルールエンジンはevalメソッドにCaseオブジェクトと引数リストと否定フラグを渡します。このCaseオブジェクトはフォームデータを間接的に含んでいます。switchReturnValuesメソッドは、少し分かりにくい部分ですが、誰かがワークフローシステム内でそれを否定に設定している場合はルールの結果を反転させます。

import java.util.*;

/**
 *
 * Check if the state the case is located in 
 * is equal to argument passed in.
 */
public class CheckStateRule implements Rule {
   private static final String EXCEPT = "Exception: ";
   String errStr;
   boolean hasMissingData;

   public boolean eval(Case c, List<String> val, boolean returnTrue){
      boolean returnVal = false;
      String argVal = null;

      try {
         if (val == null || val.size() != 1) {
            errStr = "Can't convert value list to a state";
            hasMissingData = true;
            return false;
         } else {
            argVal = (String)val.get(0);
         }

         // get the location object off the form 
         // and get the state the case resides in
         Location loc = (Location)c.getFormComposite("location");
         if (loc == null) {
            errStr
             = "Cannot determine the state this case is located in.";
            hasMissingData = true;
            return false;
         }
         String st = loc.getAttribute("state");
         String state = (st == null ? null : st.toUpperCase());
         if (state == null) {
            errStr
             = "Cannot determine the state this case is located in.";
            hasMissingData = true;
            return false;
         } else if (state.equals(argVal)) {
            returnVal = true;
         }

         // return value
         List<Object> returns
               = switchReturnValues(returnTrue, returnVal,
                 "Case does not reside in '" + argVal + "'",
                 "Case resides in '" + argVal + "'");
         returnVal = ((Boolean)returns.get(0)).booleanValue();
         errStr = (String)returns.get(1);

      } catch (Exception e) {
         errStr = EXCEPT + e.getMessage();
         hasMissingData = true;
         return false;
      }

      return returnVal;

   }

   public List<Object> switchReturnValues(boolean returnTrue,
         boolean returnVal, String falseMsg, String trueMsg)
         throws Exception {

      if (!returnTrue)
         returnVal = !returnVal;

      String retErrStr = null;
      if (!returnVal) {
         if (returnTrue)
            retErrStr = falseMsg;
         else
            retErrStr = trueMsg;
      }

      ArrayList<Object> returns = new ArrayList<Object>();
      returns.add(new Boolean(returnVal));
      returns.add(retErrStr);
      return returns;
   }

   public boolean isMissingData() {
      return hasMissingData;
   }

   public String getError() {
      return errStr;
   }
}

 CheckStateRuleクラスができたら、続いて2番目のルールであるCheckDependentsRuleクラスに着手します。このルールの機能は、フォーム上で指定された人物が間違いなく姓と名の両方を持つようにすることです。

import java.util.*;

/**
 *
 * Ensure all dependent detail is supplied
 */
public class CheckDependentsRule implements Rule {
   private static final String EXCEPT = "Exception: ";
   private String errorMessage;
   private boolean missingData = false;

   public boolean eval(Case c, List<String> val, boolean returnTrue){
      boolean returnVal = false;

      try {
         // get the location object off the form 
         // and get the state the case resides in
         Subscriber sub
           = (Subscriber)c.getFormComposite("subscriber");
         if (sub == null) {
            errorMessage = "Unable to obtain subscriber info.";
            missingData = true;
            return false;
         }

         returnVal = true;
         for (int i = 1; i <= Subscriber.DEPENDENT_ROWS; i++) {
            if (!hasCompleteDependentName(sub, i)) {
               returnVal = false;
               break;
            }
         }

         boolean returnVal1 = returnVal;
         if (!returnTrue)
            returnVal1 = !returnVal1;

         String retErrStr = null;
         if (returnVal1 == false) {
            if (returnTrue)
               retErrStr = "Dependent info incomplete";
            else
               retErrStr = "All dependent info provided";
         }

         List<Object> res = new ArrayList<Object>();
         res.add(new Boolean(returnVal1));
         res.add(retErrStr);

         // return value
         List<Object> returns = res;
         returnVal = ((Boolean)returns.get(0)).booleanValue();
         errorMessage = (String)returns.get(1);

      } catch (Exception e) {
         errorMessage = EXCEPT + e.getMessage();

         missingData = true;
         return false;
      }

      return returnVal;

   }

   private boolean hasCompleteDependentName(Subscriber sub,
                                                int number) {
      String firstName = sub.getAttribute("dep" + number + "first");
      String lastName = sub.getAttribute("dep" + number + "last");

      if ((!isEmpty(firstName) && isEmpty(lastName)) ||
            (!isEmpty(lastName) && isEmpty(firstName))) {
         errorMessage
          = "Complete name must be specified for dependent "
            + number;
         return false;
      }
      return true;
   }

   private boolean isEmpty(String string) {
      return string == null || string.equals("");
   }

   public boolean isMissingData() {
      return missingData;
   }

   public String getError() {
      return errorMessage;
   }
}

  • ブックマーク
  • 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-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5