Stateパターンの例
アプリケーションを作成する上で条件ロジックを欠くことはできません。しかし、あまりに多用すると入り組んできて分かりにくくなります。私が作成する多くのアプリケーションでは、オブジェクトはさまざまな状態で存在し、状態ごとにそれぞれ異なる動作をします。if文と複雑な条件分岐を多用する「直球型」の実装では、すぐに複雑怪奇なソリューションになってしまいます。このような事態をできるだけ避けるために、私はStateデザインパターンを使ってコードをすっきりさせています。
図書館の取り置き本を例に説明しましょう。取り置き本(Holding)オブジェクトは、本(Book)オブジェクト(リスト1を参照)のコピーです(今回の実装では、BookはISBNの分類情報にすぎません。従って、各Holdingオブジェクトはコピー番号とBookオブジェクトを参照します)。Holdingオブジェクトの状態として、貸し出しと返却があります。図書館と図書館の間での移動もあります。利用者に所持されたり、保管庫に入れられたりもします。これらの各イベントによって、Holdingオブジェクトはさまざまな規則が適用される状態に遷移します。例えば、「貸し出し中」状態の本を保管庫で保管することはできません。
// BookTest.java import static org.junit.Assert.*; import org.junit.*; public class BookTest { public static final Book CATCH22 = new Book("0-671-12805-1", "Catch-22", "Heller, Joseph", "1961"); @Test public void create() { assertEquals("0-671-12805-1", CATCH22.getIsbn()); assertEquals("Catch-22", CATCH22.getTitle()); assertEquals("Heller, Joseph", CATCH22.getAuthor()); assertEquals("1961", CATCH22.getYear()); } } // Book.java public class Book { private final String isbn; private final String title; private final String author; private final String year; public Book(String isbn, String title, String author, String year) { this.isbn = isbn; this.title = title; this.author = author; this.year = year; } public String getIsbn() { return isbn; } public String getTitle() { return title; } public String getAuthor() { return author; } public String getYear() { return year; } }
リスト2に、Holdingの最初の実装を示します(利用者IDとの関連はまだ考慮していません)。
import static org.junit.Assert.*; import java.util.Date; import org.junit.*; public class HoldingTest { private Holding holding; private static final Date NOW = new Date(); private static final String PATRON_ID = "12345"; @Before public void initialize() { Book book = BookTest.CATCH22; int copyNumber = 1; holding = new Holding(book, copyNumber); } @Test public void create() { assertSame(BookTest.CATCH22, holding.getBook()); assertEquals(1, holding.getCopyNumber()); assertFalse(holding.isOnLoan()); } @Test public void checkout() { holding.checkout(NOW, PATRON_ID); assertTrue(holding.isOnLoan()); assertEquals(NOW, holding.getLoanDate()); } @Test public void checkin() { Date later = new Date(NOW.getTime() + 1); holding.checkout(NOW, PATRON_ID); holding.checkin(later); assertFalse(holding.isOnLoan()); } } // Holding.java import java.util.Date; public class Holding { private final Book book; private final int copyNumber; private Date checkoutDate; 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) { checkoutDate = date; } public void checkin(Date date) { checkoutDate = null; } }