Following up on my post on Six Design Patterns to Start With, I mentioned the State pattern in regards to its similarity to the Strategy pattern. It might not come up that often, but it’s very effective in creating cleaner code in certain situations.
From www.dofactory.com, the State pattern is
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Many objects need to behave differently in several ways when some sort of internal state changes. The State pattern isolates the divergent functionality into swappable classes implementing a common interface. The main object simply delegates to its internal state object at the appropriate times. When the “state” property of the main object changes, it simply has to replace its internal state member with a different object.
Let’s jump into an example. For the last three years or so I’ve been using a data access layer framework I’ve written (and rewritten) to do very basic data access. The Facade for the data access layer is called DataSession. Among other things, DataSession wraps and manages all access to the underlying ADO.Net connection and transaction objects. What I came up with was a design that allowed consumers of a DataSession to just drop off an IDbCommand for execution not have to worry about transaction boundaries or IDbTransaction objects. The public interface of IDataSession looks like this:
[PluginFamily("Default")]
public interface IDataSession
{
bool IsInTransaction { get; }
void BeginTransaction();
void CommitTransaction();
void RollbackTransaction();
int ExecuteCommand(IDbCommand command);
int ExecuteSql(string sql);
IDataReader ExecuteReader(IDbCommand command);
IDataReader ExecuteReader(string sql);
DataSet ExecuteDataSet(IDbCommand command);
object ExecuteScalar(IDbCommand command);
ICommandCollection Commands { get; }
IReaderSourceCollection ReaderSources { get; }
void Initialize(IInitializable initializable);
}
Internally, the ADO.Net object usage varies a little bit between an “autocommit” mode where each command is its own transaction and a full blown transactional mode. We could write if/then logic inside each method in DataSession to check if it is in a transactional state, but that will make each method longer, messier, and create some duplication. This is where the State pattern comes in to play with an interface called IExecutionState.
public interface IExecutionState
{
int Execute(IDbCommand command);
IDataReader ExecuteReader(IDbCommand command);
DataSet ExecuteDataSet(IDbCommand command);
object ExecuteScalar(IDbCommand command);
}
DataSession has a private field called _currentState that holds an IExecutionState object. Take a look at this method in DataSession:
public int ExecuteCommand(IDbCommand command)
{
try
{
return _currentState.Execute(command);
}
catch (Exception ex)
{
// Just wraps the ADO exception with a full
// description of the IDbCommand that failed,
// including parameter values
throw new CommandFailureException(command, ex);
}
}
All it does is delegate to the active State object and provide some consistent exception handling. So, how does the State object get set? First off, the DataSession starts with an AutoCommitExecutionState object in the _currentState field. The state object is replaced when a transaction is started.
public void BeginTransaction()
{
if (IsInTransaction)
{
throw new ApplicationException("A transaction is already started!");
}
_transactionalState.BeginTransaction();
// Replace the _currentState with the transactional state
_currentState = _transactionalState;
}
Then return to the “autocommit” state whenever the transaction is committed or rolled back.
public void CommitTransaction()
{
if (!IsInTransaction)
{
throw new ApplicationException("A transaction is not started!");
}
_transactionalState.CommitTransaction();
// Replace the _currentState with the default "Auto Commit" state
_currentState = _defaultState;
}
What did the usage of the State pattern here accomplish? From my perspective it eliminated a lot of if/then logic and greatly simplified the DataSession class. It also isolated the transactional code in a single spot where it became easier to find later, and that’s awfully important.
Some Other Examples
Occasionally connected Smart Clients are a hot topic the past couple years. At several points the smart client has to behave differently based on whether or not its connected to its backend or running locally. I’ve never done this, but I bet this would be a perfect opportunity to use a State pattern.
I’ve used the State pattern effectively with user interface screens. Say you have a screen that enables users to edit some sort of business entity. The screen will probably need to behave differently based on whether or not the entity is being created, viewed, or edited. On a project a couple years ago we were able to reuse the same screen to perform several different actions by making the screen controller class delegate to an internal State object for certain operations within the normal screen flow. It enabled us to collapse a couple screens down to one and allowed us to add additional actions to the screen with less effort. This might be relevant to my current project. We are building a little application to manage a simple workflow of a new type of business entity with a couple different steps. We might be able to utilize a state pattern that enables or disables screen actions based on the current state of the business entity and the type of user using the screen.
Advantages of the State Pattern
- Kill off if/then statements. This is one of the primary goals of many of the original Gang of Four patterns, and it’s a worthy goal. If/then branching can breed bugs.
- Reduces duplication by eliminating repeated if/then or switch statements, same as its close cousin the Strategy pattern.
- Increased cohesion. By aggregating state specific behavior into State classes with intention revealing names it’s easier to find that specific logic, and it’s all in one place.
- Potential extensibility. Going back to DataSession, I might want to add some sort of asynchronous or delayed caching mode to DataSession. I could do that by implementing a new version of the IExecutionState interface.
- Better testability. We could have solved the DataSession state problem by using inheritance and creating separate AutoCommitDataSession and TransactionalDataSession classes, but the old “favor composition over inheritance” rule in OO design says that’s not the best idea and I agree. I was able to unit test the inner workings of DataSession effectively by using mock objects to test the interaction between DataSession and its IExecutionState object without touching a database.