State

Intent

Problem

A monolithic object's behavior is a function of its state, and it must change its behavior at run-time depending on that state. Or, an application is characterixed by large and numerous case statements that vector flow of control based on the state of the application.

Structure Summary

Structure category:  
wrapper + inheritance hierarchy
Similar patterns:   Builder   Bridge   Observer

Discussion

The State pattern is a solution to the problem of how to make behavior depend on state. The State pattern does not specify where the state transitions will be defined. The choices are two: the "context" object, or each individual State derived class. The advantage of the latter option is ease of adding new State derived classes. The disadvantage is each State derived class has knowledge of (coupling to) its siblings, which introduces dependencies between subclasses. [GoF, p308]

A table-driven approach to designing finite state machines does a good job of specifying state transitions, but it is difficult to add actions to accompany the state transitions. The pattern-based approach uses code (instead of data structures) to specify state transitions, but it does a good job of accomodating state transition actions. [GoF, p308]

Structure

The state machine's interface is encapsulated in the "wrapper" class. The wrappee hierarchy's interface mirrors the wrapper's interface with the exception of one additional parameter. The extra parameter allows wrappee derived classes to call back to the wrapper class as necessary. Complexity that would otherwise drag down the wrapper class is neatly compartmented and encapsulated in a polymorphic hierarchy to which the wrapper object delegates.

Example

The State pattern allows an object to change its behavior when its internal state changes. This pattern can be observed in a vending machine. Vending machines have states based on the inventory, amount of currency deposited, the ability to make change, the item selected, etc. When currency is deposited and a selection is made, a vending machine will either deliver a product and no change, deliver a product and change, deliver no product due to insufficient currency on deposit, or deliver no product due to inventory depletion. [Michael Duell, "Non-software examples of software design patterns", Object Magazine, Jul 97, p54]

Check list

  1. Identify an existing class, or create a new class, that will serve as the "state machine" from the client's perspective. That class is the "wrapper" class.
  2. Create a State base class that replicates the methods of the state machine interface. Each method takes one additional parameter: an instance of the wrapper class. The State base class specifies any useful "default" behavior.
  3. Create a State derived class for each domain state. These derived classes only override the methods they need to override.
  4. The wrapper class maintains a "current" State object.
  5. All client requests to the wrapper class are simply delegated to the current State object, and the wrapper object's this pointer is passed.
  6. The State methods change the "current" state in the wrapper object as appropriate.

Before and after

BeforeAfter
// 3-speed ceiling fan state machine

// Not good: unwieldy "case" statement

class CeilingFanPullChain {
   private int m_current_state;

   public CeilingFanPullChain() {
      m_current_state = 0;
   }
   public void pull() {
      if (m_current_state == 0) {
         m_current_state = 1;
         System.out.println( "   low speed" );
      } else if (m_current_state == 1) {
         m_current_state = 2;
         System.out.println( "   medium speed" );
      } else if (m_current_state == 2) {
         m_current_state = 3;
         System.out.println( "   high speed" );
      } else {
         m_current_state = 0;
         System.out.println( "   turning off" );
}  }  }

public class StateDemo {
   public static void main( String[] args ) {
      CeilingFanPullChain chain =
                     new CeilingFanPullChain();
      while (true) {
         System.out.print( "Press " );
         get_line();
         chain.pull();
   }  }
   static String get_line() {
      BufferedReader in = new BufferedReader(
            new InputStreamReader( System.in ));
      String line = null;
      try {
         line = in.readLine();
      } catch (IOException ex) {
         ex.printStackTrace();
      }
      return line;
}  }

// Press 
//    low speed
// Press 
//    medium speed
// Press 
//    high speed
// Press 
//    turning off
// Press 
//    low speed
// Press 
//    medium speed
// Press 
//    high speed
// Press 
//    turning off
  
// The CeilingFanPullChain class is now a wrapper
// that delegates to its m_current_state reference.
// Each clause from the "before" case statement is
// now captured in a State derived class.

// For this simple domain, the State pattern is
// probably over-kill.

class CeilingFanPullChain {
   private State m_current_state;

   public CeilingFanPullChain() {
      m_current_state = new Off(); }
   public void set_state( State s ) {
      m_current_state = s; }
   public void pull() {
      m_current_state.pull( this ); }
}

interface State {
   void pull( CeilingFanPullChain wrapper );
}

class Off implements State {
   public void pull( CeilingFanPullChain wrapper ) {
      wrapper.set_state( new Low() );
      System.out.println( "   low speed" );
}  }
class Low implements State {
   public void pull( CeilingFanPullChain wrapper ) {
      wrapper.set_state( new Medium() );
      System.out.println( "   medium speed" );
}  }
class Medium implements State {
   public void pull( CeilingFanPullChain wrapper ) {
      wrapper.set_state( new High() );
      System.out.println( "   high speed" );
}  }
class High implements State {
   public void pull( CeilingFanPullChain wrapper ) {
      wrapper.set_state( new Off() );
      System.out.println( "   turning off" );
}  }

public class StateDemo {
   public static void main( String[] args ) {
      CeilingFanPullChain chain =
                   new CeilingFanPullChain();
      while (true) {
         System.out.print( "Press " );
         get_line();
         chain.pull();
   }  }
   static String get_line() {
      BufferedReader in = new BufferedReader(
            new InputStreamReader( System.in ));
      String line = null;
      try {
         line = in.readLine();
      } catch (IOException ex) {
         ex.printStackTrace();
      }
      return line;
}  }

Rules of thumb

State objects are often Singletons. [GoF, p313]

Flyweight explains when and how State objects can be shared. [GoF, p313]

Interpreter can use State to define parsing contexts. [GoF, p349]

State is like Strategy except in its intent. [Coplien, C++ Report, Mar 96, p88]

Strategy has 2 different implementations, the first is similar to State. The difference is in binding times (Strategy is a bind-once pattern, whereas State is more dynamic). [Coplien, C++ Report, Mar 96, p88]

State, Strategy, Bridge (and to some degree Adapter) have similar solution structures. They all share elements of the "handle/body" idiom [Coplien, Advanced C++, p58; see discussion under Bridge pattern]. They differ in intent - that is, they solve different problems.

The structure of State and Bridge are identical (except that Bridge admits hierarchies of envelope classes, whereas State allows only one). The two patterns use the same structure to solve different problems: State allows an object's behavior to change along with its state, while Bridge's intent is to decouple an abstraction from its implementation so that the two can vary independently. [Coplien, C++ Report, May 95, p58]

The implementation of the State pattern builds on the Strategy pattern. The difference between State and Strategy is in the intent. With Strategy, the choice of algorithm is fairly stable. With State, a change in the state of the "context" object causes it to select from its "palette" of Strategy objects. [Coplien, Multi-Paradigm Design for C++, Addison-Wesley, 1999, p253]