Introduction
The State Pattern is a common Object Oriented Design Pattern that allows an object to alter its behavior dynamically by changing its internal state. So the State Pattern is a design pattern; what is a design pattern? A design pattern is an example "blueprint" of a solution to solve a common problem. Why use a design pattern? Design patterns are proven and take design constraints and other factors into account.
The State Pattern can be used in many different circumstances. For instance, a car's automatic transmission must behave differently depending on its speed "state". The difference in behavior is represented by the gears or states. In my example, I use a common machine that has a normal operating range, a warning range, and an alert range. The machine will behave differently depending on its range. The objects behavior is delegated to NormalState, WarningState, and AlertState. I represent the difference in behavior with color schemes and text. The color schemes used are:
- Green - "Normal"
- Yellow - "Warning"
- Alert - "Alert"
Consider most operating machines; they have a normal, warning, and alert state. For example, a control system "machine" in a power plant such as a Nuclear, Fossil, or Combined Cycle Plant has normal operating ranges and ranges that would trigger an alarm. Operators and/or engineers might want to know the state of certain machines such as a transformer, boiler, etc. While the machine is in operation and running efficiently, it should be in a normal state. If something goes wrong a warning or alert should go off and the users should be alerted. This application demonstrates a simple way of achieving this via the State Pattern.
Class diagram
State - Abstract class
This class defines the interface for the derived classes:
using System;
using System.Drawing;
namespace StatePatternApp
{
public abstract class State
{
public State()
{
}
public State(State state)
{
this.CurrentLevel = state.CurrentLevel;
this.Deviation = state.Deviation;
this.MaxLevel = state.MaxLevel;
this.MinLevel = state.MinLevel;
this.Machine = state.Machine;
this.SetParams();
}
public State(Machine machine, int iCurrentLevel,
int iDeviation, int iMaxLevel, int iMinLevel)
{
this.CurrentLevel = iCurrentLevel;
this.Deviation = iDeviation;
this.MaxLevel = iMaxLevel;
this.MinLevel = iMinLevel;
this.Machine = machine;
this.SetParams();
}
private Machine machine;
private int minLevel = 0;
private int maxLevel = 0;
private int currentLevel = 0;
private int deviation = 0;
private Color messageColor = Color.Green;
private string messageText = "";
public virtual void SetParams(){}
public virtual void CheckEfficiency()
{
Transition t = Transition.GetInstance();
t.Transform(this);
}
protected virtual void ChangeState(Machine m, State s)
{
m.ChangeState(s);
}
public Machine Machine
{
get
{
return this.machine;
}
set
{
this.machine = value;
}
}
public int MinLevel
{
get
{
return this.minLevel;
}
set
{
this.minLevel = value;
}
}
public int MaxLevel
{
get
{
return this.maxLevel;
}
set
{
this.maxLevel = value;
}
}
public int CurrentLevel
{
get
{
return this.currentLevel;
}
set
{
this.currentLevel = value;
if(this.Machine != null)
{
this.CheckEfficiency();
}
}
}
public int Deviation
{
get
{
return this.deviation;
}
set
{
this.deviation = value;
}
}
public int MaxDeviaton
{
get
{
return this.MaxLevel - this.Deviation;
}
}
public int MinDeviaton
{
get
{
return this.MinLevel + this.Deviation;
}
}
public Color MessageColor
{
get
{
return this.messageColor;
}
set
{
this.messageColor = value;
}
}
public string MessageText
{
get
{
return this.messageText;
}
set
{
this.messageText = value;
}
}
}
}
Transition class
I decided to move the transition logic from each state object to a transition class "Singleton
". This allows the application to be more maintainable and scalable. The transition
class has a Transform
method that determines which state to transition to, if needed. *** Thanks to Marc Clifton for pointing out the redundancy of having the transition logic in each state object.
using System;
namespace StatePatternApp
{
class Transition
{
private static Transition getInstance;
protected Transition() {}
public static Transition GetInstance()
{
if(getInstance == null)
{
getInstance = new Transition();
}
return getInstance;
}
public void Transform(State state)
{
if(state == null)
{
return;
}
string stateType = state.GetType().Name;
if(state.CurrentLevel < state.MaxDeviaton &&
state.CurrentLevel > state.MinDeviaton)
{
if(stateType.ToUpper() != "NORMALSTATE")
{
state.ChangeState(state.Machine,
new NormalState(state));
}
}
if(state.Deviation > 0)
{
if((state.CurrentLevel < state.MaxLevel &&
state.CurrentLevel >= state.MaxDeviaton) ||
state.CurrentLevel > state.MinLevel &&
state.CurrentLevel <= state.MinDeviaton)
{
if(stateType.ToUpper() != "WARNINGSTATE")
{
state.ChangeState(state.Machine,
new WarningState(state));
}
}
}
if(state.CurrentLevel >= state.MaxLevel ||
state.CurrentLevel <= state.MinLevel)
{
if(stateType.ToUpper() != "ALERTSTATE")
{
state.ChangeState(state.Machine,
new AlertState(state));
}
}
}
}
}
NormalState - derived state class
Implements a normal behavior. Displays Green and states "Normal":
using System;
using System.Drawing;
namespace StatePatternApp
{
public class NormalState : State
{
public NormalState(State state) : base(state)
{
}
public NormalState(Machine machine, int iCurrentLevel,
int iDeviation, int iMaxLevel, int iMinLevel) :
base(machine, iCurrentLevel, iDeviation,
iMaxLevel, iMinLevel)
{
}
public override void SetParams()
{
this.MessageColor = Color.Green;
this.MessageText = "Normal";
}
}
}
WarningState - derived state class
Implements a warning behavior. Displays Yellow and states "Warning":
using System;
using System.Drawing;
namespace StatePatternApp
{
public class WarningState : State
{
public WarningState(State state) : base(state)
{
}
public WarningState(Machine machine, int iCurrentLevel,
int iDeviation, int iMaxLevel, int iMinLevel) :
base(machine, iCurrentLevel, iDeviation, iMaxLevel,
iMinLevel)
{
}
public override void SetParams()
{
this.MessageColor = Color.Yellow;
this.MessageText = "Warning";
}
}
}
AlertState - derived state class
Implements an alert behavior. Displays Red and states "Alert":
using System;
using System.Drawing;
namespace StatePatternApp
{
public class AlertState : State
{
public AlertState(State state) : base(state)
{
}
public AlertState(Machine machine, int iCurrentLevel,
int iDeviation, int iMaxLevel, int iMinLevel) :
base(machine, iCurrentLevel, iDeviation, iMaxLevel,
iMinLevel)
{
}
public override void SetParams()
{
this.MessageColor = Color.Red;
this.MessageText = "Alert";
}
}
}
Machine class
This class maintains an instance of state:
using System;
namespace StatePatternApp
{
public class Machine
{
public State currentState;
public Machine(int iCurrentLevel, int iDeviation,
int iMaxLevel, int iMinLevel)
{
currentState = new NormalState(this, iCurrentLevel,
iDeviation, iMaxLevel, iMinLevel);
currentState.CheckEfficiency();
}
public void ChangeState(State setState)
{
currentState = setState;
}
public void SetCurrentLevel(int level)
{
currentState.CurrentLevel = level;
}
}
}
Now that you have seen an example of the State Pattern, you may have probably realized many places where you can implement this. Although the State Pattern is very powerful, it can be overly complex in some situations and in such situations flags or polymorphism could be used instead. The State Pattern may have an initial impact upfront but is more maintainable and more productive over time.