Introduction
In 1990, I read my first C++ programming book. I think it was by Herb Schildt. Because I had only programmed in non-OOP languages before then, it took me a while to get includes and understand how cout << "Hello, World!" << endl; worked exactly. After having those first few ah-ha! moments (as Grady Booch calls them), I was hooked. I got it. C++ and OOP were about combining data and behaviors, and in the real world this is a very good analogous model because many, many things have both state and behaviors.
The same exuberance I felt about C++ and OOP I now have for all things architectural and qualitative, especially modeling, patterns, anti-patterns, and refactoring. Modeling is simply using pictures as a means of convey a lot of easy to produce meaning in a tangible yet changeable way. Refactoring is a bit like mathematical factoring; refactoring describes how to simplify and improve code in a constrained, predictable manner. Finally, there are patterns and anti-patterns. Patterns are almost exactly what the word means. A pattern is an existing solution that can be reused to solve a problem. The idea is that software development is comprised of many common kinds of problems and a pattern is a good general solution. Patterns, then, refers to more than one pattern. Anti-patterns are patterns that have been demonstrated not to work.
Collectively, in a real tangible way we now have several more tools to work with. We can say that good code is refactored code, that good designs have models that make sense and employ known patterns, and avoid known anti-patterns. As a result, more of the subjectivity of software development—my code is good because I say so—is removed from software development and more people share the same standard. Of course, we aren't there yet.
If any or all of these things—patterns, anti-patterns, refactoring, or modeling—are new to you, I understand. In this article, I want to introduce just one pattern that is relatively easy to use and has a pretty high cool factor. This pattern is the State behavior pattern.
State As Behavior
The State pattern permits an object to change its behavior dynamically by changing its internal state, creating the illusion that the object has changed. What I like about the State pattern is that it is a flag killer.
To better understand how the State pattern is used, let's begin with the kind of code it can replace.
Loathsome Flags
A flag is typically an enumerated type, integer, or Boolean that conveys meaning about the mode or state of an object or is used as a pivotal point to orchestrate logic. Generally, an enumerated value conveys more meaning than a plain old integer and would be considered the best of this weakest technique.
In Listing 1, there is a class named CounterWithFlag that prints some text in German or English. Change the value of the English property (the flag) to change the behavior of the method Count.
Listing 1: Using a flag to change behavior.
Public Class CounterWithFlag Private FEnglish As Boolean = True Public Property English() As Boolean Get Return FEnglish End Get Set(ByVal Value As Boolean) FEnglish = Value End Set End Property Public Sub Count() If (FEnglish) Then DoCountInEnglish() Else DoCountInGerman() End If End Sub Private Sub DoCountInGerman() Console.WriteLine("Ein") Console.WriteLine("Zwei") Console.WriteLine("Drei") End Sub Private Sub DoCountInEnglish() Console.WriteLine("One") Console.WriteLine("Two") Console.WriteLine("Three") End Sub End Class
There is a lot of code in the world written just like this. The flags become increasingly difficult to maintain as the problem and flags grow in complexity and number. Use this approach with three or four flags and you will have to master predicate calculus reduce nested conditional statements and repeated logic. Of the three solutions I will show you, this is the weakest and should only be used for very simple problems.
Polymorphism is Better
Of the possible ways to solve the problem presented in Listing 1—simulating perhaps a multi-language implementation—polymorphism is another possible solution. With polymorphism, we can implement an abstract base class or interface and then change the instance of the class or refer to alternate interfaces depending on the desired behavior. Listing 2 shows the polymorphic implementation.
Listing 2: Bi-lingual polymorphic counting.
Public MustInherit Class Counter Public MustOverride Sub Count() End Class Public Class EnglishCounter Inherits Counter Public Overrides Sub Count() Console.WriteLine("One") Console.WriteLine("Two") Console.WriteLine("Three") End Sub End Class Public Class GermanCounter Inherits Counter Public Overrides Sub Count() Console.WriteLine("Ein") Console.WriteLine("Zwei") Console.WriteLine("Drei") End Sub End Class
In Listing 2, we define an abstract base class, Counter, and implement two variants of Counter, EnglishCounter and GermanCounter. Listing 2 demonstrates how we can print in English or German by using the flag-version first and the polymorphic version second.
Listing 3: Flags versus polymorphism.
' The first example uses a loathsome flag Dim a As CounterWithFlag = New CounterWithFlag a.Count() Console.ReadLine() a.English = False a.Count() Console.ReadLine() ' The second example uses polymorphism Dim b As Counter = New EnglishCounter b.Count() Console.ReadLine() b = New GermanCounter b.Count() Console.ReadLine()
In the first half of the listing, we have to change and keep track of the flag, which controls the behavior of count. Flags can result in extremely confusing code. In the second half, we literally have to change the instance of the object, but, once changed, the code is not predicated on a flag and will be easier to maintain and always perform correctly. The second, polymorphic, approach is superior in most circumstances to the flags approach. The third solution employs the State Pattern.