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