Design Patterns - State and Strategy

15 July 2022 at 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we discussed the memento and observer design patterns, which fall under the category of behavioral design patterns. Today, we will explore two more design patterns from the same category called State and Strategy.

Table of contents

  1. Introduction to State Design Pattern
  2. Implementation of State Design Pattern
  3. Introduction to Strategy Design Pattern
  4. Implementation of Strategy Design Pattern
  5. Conclusion

Introduction to State Design Pattern

The state design pattern helps in altering the behavior of an object when its dependent objects are modified without any intervention. This means the change in the behavior of the object does not affect the client who is consuming it. It is achieved by having lesser decision-making statements.

Implementation of State Design Pattern

The State design pattern mainly consists of:

  • Context
  • State
  • Concrete States

Here is a template for implementing State Design Pattern -

using System;

namespace PARTECH_State
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var context = new Context(new ConcreteStateA());
            context.Request();
            context.Request();
            context.Request();
            context.Request();
            Console.ReadLine();
        }
    }

    public class Context
    {
        private IState state;

        public Context(IState state)
        {
            this.state = state;
        }
        public IState State
        {
            get { return state; }
            set { state = value; }
        }

        public void Request()
        {
            state.Handle(this);
        }
    }

    public interface IState
    {
        void Handle(Context context);
    }

    public class ConcreteStateA: IState
    {
        public void Handle(Context context)
        {
            Console.WriteLine("Handling modified from Concrete State A.");
            context.State = new ConcreteStateB();
            Console.WriteLine($"State switched to { context.State.GetType().Name }");
        }
    }

    public class ConcreteStateB : IState
    {
        public void Handle(Context context)
        {
            Console.WriteLine("Handling modified from Concrete State B.");
            context.State = new ConcreteStateA();
            Console.WriteLine($"State switched to { context.State.GetType().Name }");
        }
    }

}

Here,

IState is an interface that provides the signature for the method. This needs to be implemented by the concrete state classes.

Context is a class that is accessed by the client. It's the responsibility of the context to invoke the switch in the state when the user requests. It consists of public property to hold the current state and a method to invoke the transition method of the state class.

Concrete State A and B are two classes that implement the interface IState and provide a definition for the method Handle. In the handle method, Concrete A has ConcreteStateB initialization, and B has A initialization. So, when the client requests the context to switch, the handle method of the current active state is called, which in turn instantiates the other State class. It then assigns the object of it to the context’s state.

The main method is the client here, where an object for context is created. And whenever there is a need, the state of the context is switched.

On executing the above code, the below output gets printed. When a method call is made using the State object in the context class, the functionality automatically gets switched when the State changes internally.

Introduction to Strategy Design Pattern

There are multiple ways to solve a problem. If you think in computing terms, there are multiple algorithms to achieve a given result. But, most of the time, we do not know whether we actually need an algorithm to solve a problem. It also depends on certain external factors like the expected output, performance, and so on.

Now, there are cases where the algorithm needed to solve a problem will be decided at the runtime. In such cases, the Strategy design pattern provides a cleaner and simpler approach to accessing the required algorithm.

Implementation of Strategy Design Pattern

The Strategy design pattern mainly consists of:

  • Context
  • Strategy
  • Concrete Strategy

Here is a template for implementing Strategy Design Pattern -

using System;
using System.Collections.Generic;
using System.Linq;

namespace PARTECH_Strategy
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var context = new Context(new ConcreteStratergyA());
            Console.WriteLine("Context initialized with Default sorting");
            context.DoAction();

            context.SetStrategy(new ConcreteStratergyB());
            Console.WriteLine("Context switched to reverse sorting");
            context.DoAction();

            Console.ReadLine();
        }
    }

    public interface IStrategy
    {
        List<int> ExecuteAlgorithm(List<int> data);
    }

    public class Context
    {
        private IStrategy strategy;

        public Context(IStrategy strategy)
        {
            this.strategy = strategy;
        }

        public void SetStrategy(IStrategy strategy)
        {
            this.strategy = strategy;
        }

        public void DoAction()
        {
            Console.WriteLine($"Sorting data using strategy { strategy.GetType().Name }");
            
            var input = new List<int>() { 66, 76, 56, 4, 5, 3 };

            var logger = "";
            input.ForEach(x => {
                logger += x.ToString() + ",";
            });
            Console.WriteLine($"Before sorting - {logger}");

            

            var result = strategy.ExecuteAlgorithm(input);

            logger = "";
            result.ForEach(x => {
                logger +=  x.ToString() + ",";
            });
            Console.WriteLine($"After sorting - {logger}");
        }
    }

    public class ConcreteStratergyA : IStrategy
    {
        public List<int> ExecuteAlgorithm(List<int> data)
        {
            return data.OrderBy(x => x).ToList();
        }
    }

    public class ConcreteStratergyB : IStrategy
    {
        public List<int> ExecuteAlgorithm(List<int> data)
        {
            return data.OrderByDescending(x => x).ToList();
        }
    }
}

Here,

IStrategy is the interface that has the declaration of the method which will hold the algorithm. Context is the class that holds the IStrategy in a property. This class holds the logic to assign the required strategy object as well as the method to re-assign the strategy object during run-time. It also has a DoAction method. In our example, we have a sample list of integers with random numbers in it. We will call the strategy object to execute the algorithm.

Concrete Strategy A and B classes implement the interface IStrategy. Each of them holds a different code to sort the provided input list of integers and return the sorted list.

The main method is the client. It has the code to create objects for Context and Concrete Objects A and B. Initially, the Context object is instantiated with the concrete Strategy A’s object.

Then the DoAction method of the context is invoked. The output is the sorted numbers in ascending order. Once executed, the set strategy method of context is called by passing the object of concrete strategy B. The DoAction method of the context is invoked again. Now the output is the sorted numbers in descending order.

Here is the output that gets generated on executing the above code -

Conclusion

And that’s what State and Strategy Behavioral design patterns are all about. Stay tuned for the next blog on the Design Patterns series.