- It allows an object to alter its behavior when it’s internal state changes.
- The object will appear to change its class.
- The state of an object is represented by the values of its instance variables.
- A client can change the state of an object by making property or method calls which in turn change the instance variables.
- These types of objects are called stateful objects.
- State is frequently a core concept in complex systems like stock trading systems, purchasing/requisition systems, document management, and especially work-flow system.
- The complexity may spread to numerous classes and to contain this complexity you use the State design pattern.
- In this pattern you encapsulate state specific behavior in a group of related classes each of which represents a different state.
- This approach reduces the need for intricate and hard-to-trace conditional if and case statements relying instead on polymorphism to implement the correct functionality of a required state transition.
- The goal of the State design pattern is to contain state-specific logic in a limited set of objects in which each object represents a particular state.
- State transition diagrams (also called state machines) are very helpful in modeling these complex systems.
- The State pattern simplifies programming by distributing the response to a state transition to a limited set of classes in which each one is a representation of a system’s state.
Participants
Context
(Appointment)
- Defines the interface of interest to clients.
- Maintains an instance of a ConcreteState subclass that defines the current state.
State
(IAppointmentState)
- Defines an interface for encapsulating the behavior associated with a particular state of the
Context
.
Concrete State
(Requested, Scheduled, CheckedIn, CheckedOut, Canceled)
- Each subclass implements a behavior associated with a state of Context.
var appointment = new Appointment("John Smith");
appointment.Proceed();
appointment.ChangeState(new CheckedOut(appointment));
appointment.Proceed();
appointment.ChangeState(new Canceled(appointment));
appointment.Proceed();
public interface IAppointmentState
{
Appointment Appointment { get; set; }
IAppointmentState NextState { get; }
bool CanChangeTo(IAppointmentState newState);
}
public class Requested : IAppointmentState
{
public Appointment Appointment { get; set; }
public IAppointmentState NextState => new Scheduled(Appointment);
public Requested(Appointment appointment) => Appointment = appointment;
public bool CanChangeTo(IAppointmentState newState)
{
var acceptedChangeTypes = new []{ typeof(Scheduled), typeof(Canceled) };
return acceptedChangeTypes.Contains(newState.GetType());
}
}
public class Scheduled : IAppointmentState
{
public Appointment Appointment { get; set; }
public IAppointmentState NextState => new CheckedIn(Appointment);
public Scheduled(Appointment appointment) => Appointment = appointment;
public bool CanChangeTo(IAppointmentState newState)
{
var acceptedChangeTypes = new[] { typeof(CheckedIn), typeof(Canceled) };
return acceptedChangeTypes.Contains(newState.GetType());
}
}
public class CheckedIn : IAppointmentState
{
public Appointment Appointment { get; set; }
public IAppointmentState NextState => new CheckedOut(Appointment);
public CheckedIn(Appointment appointment) => Appointment = appointment;
public bool CanChangeTo(IAppointmentState newState)
{
var acceptedChangeTypes = new[] { typeof(Scheduled), typeof(Canceled) };
return acceptedChangeTypes.Contains(newState.GetType());
}
}
public class CheckedOut : IAppointmentState
{
public Appointment Appointment { get; set; }
public IAppointmentState NextState => null;
public CheckedOut(Appointment appointment) => Appointment = appointment;
public bool CanChangeTo(IAppointmentState newState)
{
var acceptedChangeTypes = new[] { typeof(CheckedIn), typeof(Canceled) };
return acceptedChangeTypes.Contains(newState.GetType());
}
}
public class Canceled : IAppointmentState
{
public Appointment Appointment { get; set; }
public IAppointmentState NextState => null;
public Canceled(Appointment appointment) => Appointment = appointment;
public bool CanChangeTo(IAppointmentState newState)
{
var acceptedChangeTypes = new[] { typeof(Requested), typeof(Scheduled), typeof(CheckedIn), typeof(CheckedOut) };
return acceptedChangeTypes.Contains(newState.GetType());
}
}
public class Appointment
{
private IAppointmentState _State;
public string PatientName { get; set; }
public Appointment(string patientName)
{
PatientName = patientName;
_State = new Requested(this);
}
public void Proceed()
{
var nextState = _State.NextState;
if (nextState == null)
Console.WriteLine("This is the final step.");
else _State = nextState;
}
public void ChangeState(IAppointmentState newState)
{
if (_State.CanChangeTo(newState))
_State = newState;
else Console.WriteLine("The current state cannot be changed to the specified state.");
}
}
Rules of Thumb
- State objects are often Singletons.
- Flyweight explains when and how State objects can be shared.
- Interpreter can use State to define parsing contexts.
- 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).
- 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.
- 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.