top of page
Writer's pictureThe Tech Platform

State Pattern : a simple implementation in C# dotnet core.

The State pattern allows an object to alter its behavior when its internal state changes. This is achieved by swapping internal state objects that implement state-dependent behavior. An object defers all statebased behavior to a dependent state subclass; this alleviates the need for a mass of case statements within methods on the object.


  • The Context is the object that has state, which is represented by an instance of the State interface. This is the single interface that client code interacts against.

  • The State represents the interface that defines the behavior depend on the state context.

  • ConcreteStateA and ConcreteStateB represent specific states in the lifetime of the Context. They implement behavior specific to these states.

To show you the state pattern in action with a concrete example, we are going to use an order object in an e-commerce system. An order is said to be in one of three states at any one time: New, Shipped, or Canceled. A new order can be shipped or cancelled. Shipped and Canceled orders cannot be cancelled or shipped.



Create a new solution named StatePattern and add a new C# library project to it named StatePattern.Model. With the project created, add a new interface named IOrderState with the following contract:

using System;

namespace StatePattern.Model
{    
    public interface IOrderState     
    {         
        bool CanShip(Order order);         
        void Ship(Order order);         
        bool CanCancel(Order order);         
        void Cancel(Order order);    
    }
}

Next add an enumeration named OrderStatus that will be used to identify which state an order is in:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.Model
{    
    public enum OrderStatus     
    {        
        New = 0, Shipped = 1,        
        Canceled = 2    
    }
}

Now you can create the actual Order class, add a new class to the project named Order, and add the code that follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.Model
{    
    public class Order    
    {        
        private IOrderState _orderState;        
        public Order(IOrderState baseState)        
        {            
            _orderState = baseState;        
        }        
        public int Id { get; set; }        
        public string Customer { get; set; }        
        public DateTime OrderedDate { get; set; }        
        public OrderStatus Status()        
        {            
            return _orderState.Status;        
        }        
        public bool CanCancel()        
        {            
            return _orderState.CanCancel(this);        
        }        
        
        public void Cancel()        
        {            
            if (CanCancel()) _orderState.Cancel(this);        
        }        
        public bool CanShip()        
        {            
            return _orderState.CanShip(this);        
        }        
        public void Ship()        
        {            
            if (CanShip()) _orderState.Ship(this);        
        }        
        internal void Change(IOrderState orderState)        
        {            
            _orderState = orderState;        
        }    
}

The first state to be created is the canceled order state. When an order is canceled, it cannot be shipped. Add a new class to the project named CanceledOrderState that implements the IOrderState interface with the code listing that follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.Model
{    
    public class OrderCanceledState : IOrderState    
    {        
        public bool CanShip(Order order) { return false; }        
        public void Ship(Order order)         
        {             
            throw new NotImplementedException("You can’t ship a canceled 
            order!");         
        }        
        public OrderStatus Status { get { return OrderStatus.Canceled; } 
        }              
        
        public bool CanCancel(Order order) { return false; }        
        public void Cancel(Order order)        
        {            
            throw new NotImplementedException( "This order is already 
            cancelled!");        
        }    
    }
}

The next state to implement is the order shipped state. Add another class to implement the IOrderState interface, and name it OrderShippedState:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.Model
{    
    public class OrderShippedState : IOrderState    
    {        
        public bool CanShip(Order order) { return false; }        
        public void Ship(Order order)         
        {            
            throw new NotImplementedException("ìYou can’t ship a shipped 
            order!");        
        }        
        public bool CanCancel(Order order)        
        {            
            return false;        
        }        
        public void Cancel(Order order)        
        {            
            throw new NotImplementedException("You can’t cancel a shipped 
            order!”");        
        }        
        public OrderStatus Status { get { return OrderStatus.Shipped; } }    
    }
}

Finally, add the last order state, which identifies a new order. Add a new class to the project named OrderNewState, which again implements the IOrderState interface as defined here:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StatePattern.Model
{    
    public class OrderNewState : IOrderState    
    {        
        public OrderStatus Status { get { return OrderStatus.New; } }        
        public bool CanCancel(Order order)        
        {            
            return true;        
        }        
        public void Cancel(Order order)        
        {            
            order.Change(new OrderCanceledState());        
        }        
        public bool CanShip(Order order) { return true; }        
        public void Ship(Order order)        
        {            
            order.Change(new OrderShippedState());        
        }        
    }
}

As you can see from this code demo, all state-dependent behavior has been moved into separate subclasses. This makes it easy to introduce a new state later and to test the state in isolation. By taking advantage of this pattern, you prevent monolithic methods that need to determine the state of the object before implementing behavior; this is typically done through a set of case of nested if-else blocks.


The state is beneficial to use when you have an object that changes behavior depending on its state. It’s also a great pattern to refactor toward when you find classes are becoming littered with conditional statements in the form of switch/case or if blocks like strategy pattern.



Resource: Medium - jean pierre Deffo Fotso


The Tech Platform

0 comments

Comments


bottom of page