Click here to Skip to main content
Click here to Skip to main content
Go to top

C# - State Pattern Example

, 23 Jun 2005
Rate this:
Please Sign up or sign in to vote.
The State Pattern is a common Object Oriented Design Pattern that allows an object to alter its behavior dynamically by changing its internal state.

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:

  1. Green - "Normal"
  2. Yellow - "Warning"
  3. 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
{
    /// <span class="code-SummaryComment"><summary>
</span>
    /// Summary description for State.
    /// <span class="code-SummaryComment"></summary>
</span>
    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;
                // Is the machine value set?
                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;
            }

            // Get the type of state.
            string stateType = state.GetType().Name;

            // Are we in normal state?
            if(state.CurrentLevel < state.MaxDeviaton && 
                     state.CurrentLevel > state.MinDeviaton)
            {
                if(stateType.ToUpper() != "NORMALSTATE")
                {
                    state.ChangeState(state.Machine, 
                                 new NormalState(state));
                }
            }
            // Are we in warning?
            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));
                    }
                }
            }
            // Are we in alert 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
{
    /// <span class="code-SummaryComment"><summary>
</span>
    /// Summary description for NormalState.
    /// <span class="code-SummaryComment"></summary>
</span>
    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
{
    /// <span class="code-SummaryComment"><summary>
</span>
    /// Summary description for WarningState.
    /// <span class="code-SummaryComment"></summary>
</span>
    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
{
    /// <span class="code-SummaryComment"><summary>
</span>
    /// Summary description for AlertState.
    /// <span class="code-SummaryComment"></summary>
</span>
    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
{
    /// <span class="code-SummaryComment"><summary>
</span>
    /// Summary description for Machine.
    /// <span class="code-SummaryComment"></summary>
</span>
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Kenny Young

United States United States
Ken currently works as the Director of Software Engineering at the Pediatrics Epidemiology Center at the University of South Florida. He is involved in the architecture and design on numerous clinical trial projects. Some of the project include:
 
1. Rare Diseases Clinical Research Network
2. TrialNet - Diabetes
3. Teddy - Diabetes
4. CCOP - Cancer
5. AIDA - Diabetes

Comments and Discussions

 
GeneralDifficult to understand PinmemberImtiaz.Ahmed25-May-11 9:59 
GeneralI disagree with the Transition class PinmemberMark Graham19-Feb-09 3:16 
Questioncould you help me to implement state pattern Pinmembernallanch_srinivas26-Nov-07 10:49 
GeneralState in XML Pinmemberjdeer27-Jun-05 11:05 
GeneralRe: State in XML PinmemberKenny Young27-Jun-05 11:18 
QuestionWhy is state logic is replicated? PinprotectorMarc Clifton24-Jun-05 3:20 
AnswerRe: Why is state logic is replicated? PinmemberKenny Young24-Jun-05 3:54 
AnswerRe: Why is state logic is replicated? PinmemberKenny Young24-Jun-05 4:24 
GeneralRe: Why is state logic is replicated? PinprotectorMarc Clifton24-Jun-05 5:01 
GeneralRe: Why is state logic is replicated? PinmemberKenny Young24-Jun-05 5:32 
GeneralRe: Why is state logic is replicated? PinprotectorMarc Clifton24-Jun-05 6:10 
GeneralSeparation of concerns PinprotectorMarc Clifton23-Jun-05 16:43 
GeneralRe: Separation of concerns PinmemberKenny Young24-Jun-05 2:46 
GeneralRe: Separation of concerns PinprotectorMarc Clifton24-Jun-05 3:10 
GeneralRe: Separation of concerns PinmemberKenny Young24-Jun-05 3:56 
GeneralRe: Separation of concerns PinprotectorMarc Clifton24-Jun-05 4:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140926.1 | Last Updated 23 Jun 2005
Article Copyright 2005 by Kenny Young
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid