It Is a Matter of State

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.

State Pattern

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.

Summary

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.

Biography

Paul Kimmel is the VB Today columnist, 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 pkimmel@softconcepts.com if you need assistance or are interested in joining the Lansing Area .NET Users Group (glugnet.org).



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds