SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

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

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

 リスト5に、4つの状態クラスを示します。各クラスは小さくて単純なので、直接簡単にテストできます。

リスト5 4つの状態クラス
// CheckedOut.java
import java.util.*;

class CheckedOut extends HoldingState {
   CheckedOut(Holding holding) {
      super(holding);
   }

   @Override
   public void checkin(Date date) {
      holding.doCheckin(date);
      holding.state = new CheckedIn(holding);
   }

   @Override
   public void placeHold(Date date, String patronId) {
      holding.doHold(date, patronId);
      holding.state = new CheckedOutHeld(holding);
   }
}

// CheckedIn.java
import java.util.*;

class CheckedIn extends HoldingState {
   CheckedIn(Holding holding) {
      super(holding);
   }

   @Override
   public void checkout(Date date, String patronId) {
      holding.doCheckout(date, patronId);
      holding.state = new CheckedOut(holding);
   }

   @Override
   public void placeHold(Date date, String patronId) {
      holding.doHold(date, patronId);
      holding.state = new CheckedInHeld(holding);
   }
}

// CheckedInHeld.java
import java.util.*;

public class CheckedInHeld extends CheckedIn {
   CheckedInHeld(Holding holding) {
      super(holding);
   }

   @Override
   public void checkout(Date date, String patronId) {
      if (patronId != holding.holdPatron)
         throw new HoldException();
      holding.doCheckout(date, patronId);
      holding.state = new CheckedOut(holding);
   }

   @Override
   public void placeHold(Date date, String patronId) {
      throw new HoldException();
   }

   @Override
   public void update(Date date) {
      holding.doReleaseOldHold(date);
   }
}

// CheckedOutHeld.java
import java.util.*;


public class CheckedOutHeld extends HoldingState {
   CheckedOutHeld(Holding holding) {
      super(holding);
   }

   @Override
   public void checkin(Date date) {
      holding.doCheckin(date);
      holding.state = new CheckedInHeld(holding);
   }

   @Override
   public void placeHold(Date date, String patronId) {
      throw new HoldException();
   }
}

 できあがったHoldingクラス(リスト6を参照)には、条件ロジックがほとんどありません。テストも非常に簡単です。

 Stateパターンは、クラス間の密接な結び付きを必要とする数少ないデザインパターンの1つです。Holdingクラスは初期状態に依存し、そして次に各状態がHoldingに依存します。これを断ち切るための優れた方法もいくつかありますが、ほとんどの場合、そうする必要はありません。なぜなら、クラスは自己閉鎖的なサブシステムであり、システムの他の部分から切り離されているからです。今回の状態クラスの実装では、Javaのパッケージレベルアクセスを利用しました。これにより、状態オブジェクトは必要に応じてHolding変数に直接アクセスできますが、これらのフィールドはHoldingの外部クライアントに公開されません。フィールドはプライベートにすべしというのが普段の私の主張なのですが、これは数少ない例外の1つです。

リスト6 Stateデザインパターンを使った場合のHolding
import java.util.*;

public class Holding {
   private final Book book;
   private final int copyNumber;

   Date checkoutDate;
   String holdPatron;
   Date holdDate;
   HoldingState state = new CheckedIn(this);
   Date checkinDate;

   public Holding(Book book, int copyNumber) {
      this.book = book;
      this.copyNumber = copyNumber;
   }

   public Book getBook() {
      return book;
   }

   public int getCopyNumber() {
      return copyNumber;
   }

   public boolean isOnLoan() {
      return checkoutDate != null;
   }

   public Date getLoanDate() {
      return checkoutDate;
   }

   public void checkout(Date date, String patronId) {
      state.checkout(date, patronId);
   }

   public void checkin(Date date) {
      state.checkin(date);
   }

   public void placeHold(Date date, String patronId) {
      state.placeHold(date, patronId);
   }

   public void update(Date date) {
      state.update(date);
   }

   public boolean isOnHold() {
      return holdPatron != null;
   }

   public void releaseAnyHold() {
      holdPatron = null;
   }

   // callback actions
   void doHold(Date date, String patronId) {
      holdPatron = patronId;
      holdDate = date;
   }

   void doCheckout(Date date, String patronId) {
      checkoutDate = date;
      releaseAnyHold();
   }

   public void doCheckin(Date date) {
      checkoutDate = null;
   }

   public void doReleaseOldHold(Date updatedAt) {
      if (DateUtil.daysBetween(holdDate, updatedAt) >= 3)
         releaseAnyHold();
   }
}

 一般に、状態遷移図に新機能を追加するのは簡単です。状態クラスが個別化されていることで、発生条件の複雑さが回避され、条件ロジックが最小限で済みます。場合によっては、変更によって状態間の遷移に変化が生じ、いくつかの状態派生クラスを更新しなければならないこともあります。状態遷移図を描くことは有益な手法ですが、遷移の管理に役立つ方法は他にもあります。

 状態遷移図は単純なテーブルとして表現できます。もっと厳密なStateパターンへとリファクタリングしていけば、作業を劇的に簡単化できるはずです。テーブルを使う場合は、イベントメソッドとコールバックアクションメソッドの名前をテーブルに定義します。これをそのまま、状態派生クラスのコードの自動作成に利用できるでしょう。非常に動的な状態システムでは、このような自動化は重要です。Object MentorのWebサイトから、このようなJavaコードジェネレータであるSMC(State Machine Compiler)をダウンロードできます。また、より動的なサブシステムで状態システムの変更を簡単化できるStateパターンのバリエーションも紹介されています。

図2 Stateパターン
図2 Stateパターン

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
デザインパターンの使い方連載記事一覧

もっと読む

この記事の著者

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件以上執筆しており、そのうちの...

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3303 2008/12/12 14:00

イベント

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

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

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

メールバックナンバー