Working With Design Patterns: State Wisconsin

The state pattern can help simplify complex conditional logic by representing individual states as classes, each with its own simple behavior.

Local Companies

Wireless Direct
1.866.707.8498
BOX 71101
shorewood, WI
Xorbix Technologies Inc.
414-277-5044
759 N. Milwaukee St.
Milwaukee, WI
Visionary Computer Solutions
262-365-9430
PO Box 406
Grafton, WI
R.E. Coker and Associates, Inc.
262-723-8104
108 W Court St.
Elkhorn, WI
IFS
414-577-5191
12000 W. Park Place
Milwaukee, WI
R S InfoCon, Inc.
262-898-7456
2320 Renaissance Blvd
Sturtevant, WI
HarrisData
262-784-9099
13555 Bishop's Court, Suite 300
Brookfield, WI
Acumium
608 310 9700 x 522
5133 West Terrace Drive Suite 300
Madison, , WI
Fischer Lyle
(920) 326-6016
W10647 Diefenbach Cir
Beaver Dam, WI
Datamatic Processing Inc of Wis
(920) 734-2647
1000 N Lynndale Dr
Appleton, WI


Working With Design Patterns: State

provided by: 
Originally published at Internet.com


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 checkoutDaate != null; } public Date getLoanDate() { return checkoutDate; } public void checkout(Date date, String patronId) { checkoutDate = date; } public void checkin(Date date) { checkoutDate = null; } }

The next story I want to tackle is to allow patrons to place a hold on a book. If checked in, the librarian places a held book behind the desk. Attempts to check it out by a different patron are rejected. If checked out, the system marks the book on hold for when it's returned. Holds are only valid for three days; a daily update routine hits all holdings, giving each an opportunity to clear any expired holds. The three-day timer starts only once a book is returned, if it's checked out. A hold cannot be placed on a book already on hold.

Based on these requirements, the code to manage patron holds (see Listing 3) really isn't that bad, but I'm starting to wonder where it's headed. I must ensure I have if statements in the right place, for example, when doing an update: If I add code to check to see whether any hold needs to be cleared, but neglect to guard against the case where the holding isn't already on hold, my code throws a NullPointerException. As I consider similar features, such as transfers and placing books on reserve, I'm thinking that the code easily could start getting unwieldy and confusing.

Tracking State

A holding can be in a number of states: checked out, checked in, on hold or not, in transit between branches, and so on. For the existing code, a holding can be in one of four possible states: * checked out, on hold * checked out, not on hold * checked in, on hold * checked out, not on hold

Defining the states allows me to draw a state diagram (see Figure 1). The state diagram captures each separate state as a separate box. A holding can transition between states when specific events occur, such as the holding being checked in. Events usually trigger actions as part of transitioning to another state.



Click here for a larger image.

Figure 1: State transitions.

The state pattern says that I can take each of these states and represent them as a separate class. The current state is tracked within Holding as a reference to a state object. I define the possible events as methods on all states. Each state handles the set of events however appropriately by invoking an action on the Holding object. The event handler also is responsible for updating the state reference on the Holding object if a state transition is required.

Listing 3: Holding, revised. // HoldingTest.java import static org.junit.Assert.*; import java.util.*; import org.junit.*; public class HoldingTest { private Holding holding; private static final Date NOW = new Date(); private static final Date LATER = new Date(NOW.getTime() + 1); private static final String PATRON_ID1 = "12345"; private static final String PATRON_ID2 = "22345"; @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_ID1); assertTrue(holding.isOnLoan()); assertEquals(NOW, holding.getLoanDate()); } @Test public void checkin() { holding.checkout(NOW, PATRON_ID1); holding.checkin(LATER); assertFalse(holding.isOnLoan()); } @Test public void placeHoldOnCheckedInHolding() { assertFalse(holding.isOnHold()); holding.placeHold(NOW, PATRON_ID1); assertTrue(holding.isOnHold()); } @Test public void rejectsDuplicateHolds() { holding.placeHold(NOW, PATRON_ID1); try { holding.placeHold(NOW, PATRON_ID2); fail(); } catch (HoldException expected) { assertTrue(holding.isOnHold()); } } @Test(expected = HoldException.class) public void holdOnCheckedOutBookRetainedOnCheckin() { holding.checkout(NOW, PATRON_ID2); holding.placeHold(NOW, PATRON_ID1); holding.checkin(LATER); assertTrue(holding.isOnHold()); holding.checkout(NOW, PATRON_ID2); } @Test public void releaseHold() { holding.placeHold(NOW, PATRON_ID1); holding.releaseAnyHold(); assertFalse(holding.isOnHold()); } @Test public void holdReleasedAfter3Days() { holding.placeHold(NOW, PATRON_ID1); holding.update(DateUtil.addDays(NOW, 1)); assertTrue(holding.isOnHold()); holding.update(DateUtil.addDays(NOW, 2)); assertTrue(holding.isOnHold()); holding.update(DateUtil.addDays(NOW, 3)); assertFalse(holding.isOnHold()); } @Test public void holdReleasedAfter3DaysAfterCheckin() { holding.checkout(NOW, PATRON_ID1); holding.placeHold(LATER, PATRON_ID1); Date checkinDate = DateUtil.addDays(NOW, 3); holding.update(checkinDate); assertTrue(holding.isOnHold()); holding.checkin(checkinDate); assertTrue(holding.isOnHold()); holding.update(DateUtil.addDays(checkinDate, 3)); assertFalse(holding.isOnHold()); } @Test public void releaseHoldHarmlessIfNoHolds() { holding.releaseAnyHold(); assertFalse(holding.isOnHold()); } @Test public void checkoutReleasesHold() { holding.placeHold(NOW, PATRON_ID1); holding.checkout(LATER, PATRON_ID1); assertFalse(holding.isOnHold()); } @Test public void rejectCheckoutForHoldByDifferentPatron() { holding.placeHold(NOW, PATRON_ID1); try { holding.checkout(NOW, PATRON_ID2); fail(); } catch (HoldException expected) { } } } // Holding.java import java.util.*; public class Holding { private final Book book; private final int copyNumber; private Date checkoutDate; private String holdPatron; private Date holdDate; 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) { if (isOnHold() && patronId != holdPatron) throw new HoldException(); releaseAnyHold(); checkoutDate = date; } public void checkin(Date date) { checkoutDate = null; } public void placeHold(Date date, String patronId) { if (isOnHold()) throw new HoldException(); this.holdPatron = patronId; holdDate = new Date(); } public boolean isOnHold() { return holdPatron != null; } public void releaseAnyHold() { holdPatron = null; } public void update(Date date) { if (isOnHold() && !isOnLoan() && DateUtil.daysBetween(holdDate, date) >= 3) releaseAnyHold(); } }

I refactor my code slowly, running tests continually. The first class I create is the HoldingState class (see Listing 4), an abstract class intended to provide null behavior for each of the possible events.

Listing 4: HoldingState. // HoldingState import java.util.*; abstract class HoldingState { protected final Holding holding; HoldingState(Holding holding) { this.holding = holding; } void checkout(Date date, String patronId) { } void checkin(Date date) { } void placeHold(Date date, String patronId) { } void update(Date date) { } }

I then create state derivatives, one for each of the three states. After I create the derivatives, I begin to move code over from the Holding class. For each event (checkout, checkin, placeHold, update), the job of the Holding object is simply to delegate the event to the current HoldingState object. For example, the checkout method in Holding ends up reading: public void checkout(Date date, String patronId) { state.checkout(date, patronId); }

Because these are simple delegations, I could consider using the Java proxy mechanism. Right now, I'm not concerned about a rapidly growing public interface on Holding, so I'll defer that enhancement.

States that need to do something with the checkout event call an action method and/or transition to another state. Other states may choose to ignore that event. One interesting result of implementing the state pattern, though, is that it may help point out a potential problem in the system by making ignored events apparent. For example, my CheckedOut state object provides no behavior for the checkout event. That unhandled event triggers me to think that I must determine what should happen if someone does try to check out a book twice.

The benefit from moving all the code into the state objects is that the conditionals begin to disappear. When in the CheckedInHeld state, I no longer have to check whether or not a book is held before I proceed: class CheckedInHeld extends HoldingState { // ... @Override public void checkout(Date date, String patronId) { if (patronId != holding.holdPatron) throw new HoldException(); holding.doCheckout(date, patronId); holding.state = new CheckedOut(holding); } // ... }

Previously, the code in checkout in Holding had to ask whether or not the book was already held. Because this code is executing when the Holding references the CheckedInHeld state, that question no longer needs to be asked.

The state classes are shown in Listing 5. Each of the state classes is small, simple, and easily could be tested directly.

Listing 5: State derivatives. // 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(); } }

The Holding class that remains (see Listing 6) is now devoid of most conditional logic! I can now test it, too, with excruciating ease.

The state pattern is one of the few design patterns that requires tight coupling between its classes: The Holding class is dependent upon the initial state, and each of the states is in turn dependent upon Holding. Although there are some clever ways to break this, it's probably not necessary, because the classes represent a self-enclosed subsystem that can remain isolated from the rest of the system. For the implementation of the state classes, I took advantage of Java's package-level access, directly having the states access Holding variables as needed, but not exposing these fields to external clients of Holding. It's one of the rare cases where I won't insist that a field remain private.

Listing 6: Holding, using the state design pattern. 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(); } }

Adding new functionality into the state diagram usually is simple and straightforward. The separate state classes help keep conditional logic to a minimum by avoiding confusion about the conditions under which something can occur. Often, modifications will necessitate changes to transitions between states, requiring updates to multiple state derivatives. Drawing a state diagram can be a good idea, but there's another possible solution to help manage the transitions.

State diagrams can be represented as simple tables. If I were to refactor to a more disciplined implementation of the state pattern, I could dramatically simplify my work. The table would define event methods and callback action method names; these would be used to automatically generate code for the state derivatives. This automation can be critical in a highly dynamic state system. The Object Mentor web site contains a download for just such a Java code generator, SMC (State Machine Compiler). There also are state pattern variants that can simplify state system changes in a more dynamic subsystem.



Cliick here for a larger image.

Figure 2: The State pattern.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff has contributed a couple chapters to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 75 articles on software development, with over thirty appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

Author: Jeff Langr

Read article at Internet.com site

Featured Local Company

IFS

414-577-5191
12000 W. Park Place
Milwaukee, WI
www.IFSWORLD.com

IFS provides solutions that enable organizations to become more agile, achieve better business performance, and maintain their competitive advantage. The company pioneered component-based ERP software with IFS Applications, now in its seventh generation. This component architecture provides solutions that are easier to implement, run, and upgrade. IFS targets seven key industries: aerospace & defense, automotive, high-tech, industrial manufacturing, process industries, construction & facilities management, and utilities & telecom.

IFS - Industries
IFS - Solutions

Regional Articles
- Working With Design Patterns: State Appleton WI
- Working With Design Patterns: State Baraboo WI
- Working With Design Patterns: State Beaver Dam WI
- Working With Design Patterns: State Beloit WI
- Working With Design Patterns: State Brookfield WI
- Working With Design Patterns: State Burlington WI
- Working With Design Patterns: State Cedarburg WI
- Working With Design Patterns: State Chippewa Falls WI
- Working With Design Patterns: State Cudahy WI
- Working With Design Patterns: State De Pere WI
- Working With Design Patterns: State Delavan WI
- Working With Design Patterns: State Eau Claire WI
- Working With Design Patterns: State Elkhorn WI
- Working With Design Patterns: State Fond Du Lac WI
- Working With Design Patterns: State Fort Atkinson WI
- Working With Design Patterns: State Franklin WI
- Working With Design Patterns: State Green Bay WI
- Working With Design Patterns: State Hartland WI
- Working With Design Patterns: State Janesville WI
- Working With Design Patterns: State Kaukauna WI
- Working With Design Patterns: State Kenosha WI
- Working With Design Patterns: State La Crosse WI
- Working With Design Patterns: State Lake Geneva WI
- Working With Design Patterns: State Manitowoc WI
- Working With Design Patterns: State Marinette WI
- Working With Design Patterns: State Marshfield WI
- Working With Design Patterns: State Menasha WI
- Working With Design Patterns: State Menomonee Falls WI
- Working With Design Patterns: State Menomonie WI
- Working With Design Patterns: State Merrill WI
- Working With Design Patterns: State Middleton WI
- Working With Design Patterns: State Milwaukee WI
- Working With Design Patterns: State Mosinee WI
- Working With Design Patterns: State Mukwonago WI
- Working With Design Patterns: State Muskego WI
- Working With Design Patterns: State Neenah WI
- Working With Design Patterns: State New Berlin WI
- Working With Design Patterns: State Oak Creek WI
- Working With Design Patterns: State Oconomowoc WI
- Working With Design Patterns: State Onalaska WI
- Working With Design Patterns: State Oshkosh WI
- Working With Design Patterns: State Pewaukee WI
- Working With Design Patterns: State Racine WI
- Working With Design Patterns: State Rhinelander WI
- Working With Design Patterns: State Rice Lake WI
- Working With Design Patterns: State River Falls WI
- Working With Design Patterns: State Schofield WI
- Working With Design Patterns: State Shawano WI
- Working With Design Patterns: State Sheboygan WI
- Working With Design Patterns: State South Milwaukee WI
- Working With Design Patterns: State Stevens Point WI
- Working With Design Patterns: State Sturgeon Bay WI
- Working With Design Patterns: State Sun Prairie WI
- Working With Design Patterns: State Superior WI
- Working With Design Patterns: State Thiensville WI
- Working With Design Patterns: State Two Rivers WI
- Working With Design Patterns: State Watertown WI
- Working With Design Patterns: State Waukesha WI
- Working With Design Patterns: State Waupaca WI
- Working With Design Patterns: State Wausau WI
- Working With Design Patterns: State West Bend WI
- Working With Design Patterns: State Whitewater WI
- Working With Design Patterns: State Wisconsin Rapids WI
Related Local Events
2008 Early Stage Symposium
Dates: 11/5/2008 - 11/6/2008
Location: Monona Terrace
Madison WI
View Details

Wisconsin Entrepreneurs' Conference
Dates: 6/9/2008 - 6/10/2008
Location: Hyatt Regency Hotel
Milwaukee WI
View Details
Rate Article
     
Articles Insider

Rss   Delicious   Digg   Add To My Yahoo   Add To My Google   Bookmark   Search Plugin

Topics:
Advertising Engineering Industrial Goods & Services Software
Business Services Family Insurance Technology
Career Financial Services Internet Telecommunications
Cars Food & Beverage Legal Transportation & Logistics
Computer Hardware Health Real Estate Travel
Construction Home Services Retail & Consumer Services Wedding
Education