PTOM: Descriptive State Enumeration
This post is part of the November 2008 Pablo’s Topic Of The Month (PTOM) – Design Patterns and will primarily outline the State pattern, with an Enumeration or Descriptor pattern thrown in for good measure.
Switch statements and if-then statements are not object oriented code. They are conditional logic for procedural control and processing. This doesn’t mean that they are bad – by no means. It only means that they are not object oriented. There are many legitimate reasons for using if-then statements and a few legitimate reasons for using switch statements. However, there are also many bad reasons for using these statements.
Setting Up Shop
Before we get into the guts of the State pattern, let’s consider the Point of Sale system of our coffee shop again. When a customer is placing an order, we need to track what they are ordering. This would likely be done through an Order object. While the customer is placing their order, the order is considered “in process”. Once the customer is done with their order, the cashier can “total” the order. Then, the order is “tendered” (paid for). And finally, the order is “delivered” to the customer. Each of these quoted items is a state that the order goes through during it’s brief run through the coffee shop. In C#, it would be very easy to represent these states with a simple enum:
With this enum in hand, we can have our point of sale system set the correct order status according to what is going on at the moment.
Now this code we have so far is technically correct – it works and it gets the job done. Unfortunately, though, this code will likely lead us down a bunch of dark paths of code duplication, ugly switch statements, and an unmaintainable mess. For example, what happens when we start integration our Order object into the rest of the coffee shop and we need to display orders to fulfill on a screen for the order runners to fill. In such a case, we only want to show orders that have been tendered but not delivered, and to try and get orders out the door faster, we’ll also show orders that are totaled. If we were going to examine a single order to determine whether or not we wanted to show it, we would likely have one of the two following pieces of code in place (yes, I know this could be done with a database query. The point is that somewhere in the code you will likely end up with switch statements or if-then statements like this):
So – what happens when the order status list change? Or if we now have a need to show a different order status on our display? We would have to find all of the locations that used this status enum and check to see if it needs to be changed, to handle the new status or change in how the status is handled. This is where the problems really start – hoping that you got all of the uses changed and all of the right states handled in the right ways. Fortunately, there’s a design pattern that we can use to simplify this situation and eliminate many of the associated switch and complex if-then statements.
The State Pattern
Wikipedia describes the State Pattern as “a clean way for an object to partially change its type at runtime.” The C2 Wiki gives a little more insight into the State Pattern, as well: “Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.”
What both of these descriptions are really getting at, is that a single concept in the real world is likely going to be modeled as more than one class in code – composed of many different pieces. When any one part of a concepts model changes, the state of that concept is changed. The relationship between our Order class and OrderStatus enum are a prime example of this composition. What the state pattern allows us to do is change the behavior of our modeled concept by partially changing the composition of our model.
Modeling Our State As State Model
To model the state of our Order with a state pattern instead of a state enum, and begin introducing the ability to change our systems behavior without changing the code that relies on the state, we need to introduce an abstraction that can represent any state we care about. To start with, we need to know what orders should be shown on our display for the order runners.
After introducing this abstraction, we can introduce any state implementation that we need.
Once we have these states in place, we can replace our code that sets the state with these objects.
And we can also significantly reduce the code that needs to know if we should display this order in our order display system. Now, instead of having to do those if-then or switch statements, we can simply check the order status:
Yes, we are still using an if-then statement. However, this one simple statement is no longer an issue in terms of maintainability. We don’t have to change this code as we add, remove, or change the states of our system. Since we only depend on the IOrderStatus interface for our composition now, we can freely change the implementation of the states as we need – change the composition of the Order’s model.
But Wait! There’s More!
We don’t have to stop at simple boolean values on our state abstraction – we can provide actual behavior via methods in our state. And the best part is – when we can add and remove any behavior or boolean values, or whatever else we need to change with the state of our Order, we don’t have to change any of the existing code that uses the existing DisplayForFullfillment value. This property is defined on the interface, letting us use it where we need it and ignore it where we don’t need it.
By using a state pattern, as we’ve seen here, we have introduced the ability to change the behavior of our system without having to change the places that need to know about the state of the order. We’ve gained a great level of encapsulation. We’ve gained flexibility in changing states when we need to – and we have an abstraction that hides the details when we don’t need them. Seems like a winning situation to me.
But Wait! There’s Still More!
Unfortunately, one thing we lose is the friendly syntax of our enumeration. For example, “OrderStatus.Totaled.” Personally – I like the simplicity that enumerations offer us in our code. Something about saying “OrderStatus.Totaled” seems to be right to me. So how do we marry the state pattern to the enumeration syntax? The answer is simple – an Enumeration pattern (also called a Descriptor pattern).
We can make a simple change to our order status abstraction – make it an abstract base class. Then we can provide some static fields to provide access to the class instances that we want. To enforce the enumeration like syntax, we can also make the constructor private.
And now we have our nice friendly enumeration syntax back!