Working With Design Patterns: State
Conditional logic is essential to building any application, yet too much can make an application incomprehensible. Many of the applications I build require that an object exist in many different states, with behavior differing from state to state. A straightforward implementation involves lots of if statements and complex conditionals, producing overly convoluted solutions in short order. As a remedy, I use the state design pattern to keep my code from getting out of hand.
Holdings in a library provide a good example. A holding is a copy of a book (see Listing 1). (In my implementation, the book is simply the ISBN classification information. Thus, each holding object references a copy number and a book object.) Holdings can be checked out, checked in, they can be moved from branch to branch, they can be held by a patron, they can be warehoused, and so on. Each of these events puts the holding into a state where different rules apply. For example, a book that's checked out obviously can't be warehoused.
Listing 1: The Book class.
// 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; } }
Listing 2 shows a starter implementation for Holding. (Note that I'm not yet concerned with the relevancy of the patron ID.)
Listing 2: An initial Holding implementation.
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; } }
Page 1 of 4