It Is a Matter of State, Page 2
The third solution employs the State pattern. In short, the state pattern is an internalization of the polymorphic solution. By employing the second solution—see Listing 2—we are making the consumer responsible for changing the instance of the object when conditions have changed. By internalizing the polymorphic, or dynamic aspects, we can permit the class to change its behavior on-the-fly while still permitting the consumer to do so. Listing 4 demonstrates the State pattern.
Listing 4: State as Behavior.
Public Enum States English German End Enum Public Class CounterWithState Private current As State = New EnglishState(Me) Public Sub Count() current.Count() End Sub Public WriteOnly Property State() As States Set(ByVal Value As States) If (Value = States.English) Then current = New EnglishState(Me) Else current = New GermanState(Me) End If End Set End Property End Class Public MustInherit Class State Private owner As CounterWithState = Nothing Public Sub New(ByVal owner As CounterWithState) Me.owner = owner End Sub Public MustOverride Sub Count() End Class Public Class GermanState Inherits State Public Sub New(ByVal owner As CounterWithState) MyBase.New(owner) End Sub Public Overrides Sub Count() Console.WriteLine("Ein") Console.WriteLine("Zwei") Console.WriteLine("Drei") End Sub End Class Public Class EnglishState Inherits State Public Sub New(ByVal owner As CounterWithState) MyBase.New(owner) End Sub Public Overrides Sub Count() Console.WriteLine("One") Console.WriteLine("Two") Console.WriteLine("Three") End Sub End Class
In listing 4, the class for general consumption is CounterWithState. The consumer creates an instance of CounterwithState and calls Count. Now, based on an internal condition, an external condition, or a combination of both, we can change the internal instance of the state field (shown bold and underlined) from EnglishState to GermanState. The end result is that CounterWithState.Count will invoke state.Count, and the internal state object determines which behavior is invoked.
It is important to note that we didn't illustrate the use of the reference to the containing object. The purpose of the state objects having a reference to its owning object is that each state object is working on behalf of its owning object. For example, if we added a property and field pair, the CounterWithState property getter and setter would be implemented in terms of a matching State getter and setter. In addition to the state object getting or setting its owning object's field, each state class' getter and setter could include different kinds of logic.
Knowing When to Use the State Pattern
When you have mastered the State Pattern, you might be inclined to use it all of the time. This is to be expected, but as you can see from Listing 4, you will incur some extra labor upfront in writing the code. The key is to find a balance.
Flags are the weakest solution but are easy to implement and work okay as long as there aren't too many flags. Polymorphism always works, but this can mean creating and keeping track of a much more complicated genealogy. Use Polymorphism to remove flags for moderately complex problems where you have semantically similar behaviors. Finally, the State Pattern is very powerful but requires the greatest understanding of OOP. You can nest the state-behavior classes to conceal them from consumers, and this pattern will permit you—the producer—as well as the consumer to dynamically change the behavior of a class. Use the State Pattern to divide complex behaviors into classes and eliminate flags altogether; however, be prepared to pay an upfront cost that may be substantially or initially greater than using flags but ultimately more maintainable and less expensive over time.
Marvin Hamlisch was playing at the Wharton Center at Michigan State University a couple of years ago. A parent asked Mr. Hamlisch to play happy birthday for a child. Rather than get upset, Mr. Hamlisch pondered whether the masters—Beethoven, Mozart, or Bach—had ever been asked this question. In response, he played happy birthday to the child by emulating the style of each of the masters. These renditions were so brilliant that for a moment the personage and music of each master composer came to life. In that moment, Marvin Hamlisch clearly demonstrated to me that he clearly had surpassed simple mechanical mastery of his craft and was able to adroitly and playfully elevate his performances to sublime art.
Knowing the difference between using flags, inheritance, and polymorphism, or employing a pattern and making a considered decision about which to use represents mastery. Choosing correctly is art.
Copyright © 2004 by Paul Kimmel. All Rights Reserved.
Paul Kimmel has written several books on .NET programming, and is a software architect. Paul enjoys flying planes, playing video games, shooting his Glock 21, and inventing new words—such as gobject, as in gobject-oriented programming, meaning a monolithic GUI object that encompasses controls, business rules, and everything else—for the human lexicon. You may contact him at email@example.com if you need assistance or are interested in joining the Lansing Area .NET Users Group (glugnet.org).