© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_15

15. Strategy Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

This chapter covers the Strategy pattern. It is also known as the Policy pattern.

GoF Definition

Define a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Concept

A client can select an algorithm from a set of algorithms dynamically at runtime. This pattern also provides a simple way to use the selected algorithm.

You know that an object can have states and behaviors. And some of these behaviors may vary among the objects of a class. This pattern focuses on the changing behaviors that can be associated with an object at a specific time.

In our example, you see a Vehicle class. You can create a vehicle object using this class. Once a Vehicle object is created, you can add and set behaviors to this object. Inside the client code, you can replace the current behavior with a new behavior too. Most interestingly, you see that since the behaviors can be changed, the vehicle class is not defining the behavior; it is simply delegating the task to an object referenced by a vehicle. The overall implementation can make the concept clearer to you.

Real-World Example

In a soccer match, if Team A is leading 1–0 over Team B toward the end of the game, instead of attacking, Team A becomes defensive to maintain the lead. At the same time, Team B goes for an all-out attack to score the equalizer.

Computer-World Example

Suppose that you have a backup memory slot. If your primary memory is full, but you need to store more data, you can use a backup memory slot. If you do not have this backup memory slot and you try to store the additional data into your primary memory, the data is discarded (when the primary memory is full). In these cases, you may get exceptions, or you may encounter some peculiar behavior (based on the architecture of the program). So, a runtime check is necessary before you store the data. Then you can proceed further.

Implementation

In this implementation, I focus on the changing behaviors of a vehicle only. In the implementation, you see that once a vehicle object is created, it is associated with an InitialBehavior, which simply states that in this state, the vehicle cannot do anything special. But once you set a FlyBehavior , the vehicle can fly. When you set the FloatBehavior , it can float. All changing behaviors are maintained in a separate hierarchy.
    /// <summary>
    /// Abstract Behavior
    /// </summary>
    public abstract class VehicleBehavior
    {
        public abstract void AboutMe(string vehicle);
    }
    /// <summary>
    /// Floating capability
    /// </summary>
    class FloatBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} can float now.");
        }
    }
    /// <summary>
    /// Flying capability
    /// </summary>
    class FlyBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} can fly now.");
        }
    }
    /// <summary>
    /// Initial behavior. Cannot do anything special.
    /// </summary>
    class InitialBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} is just born.It cannot do anything special.");
        }
    }
In many examples, you see a term called a context class . Vehicle is the context class in this demonstration. This class is defined as follows.
    /// <summary>
    /// Context class-Vehicle
    /// </summary>
    public class Vehicle
    {
        VehicleBehavior behavior;
        string vehicleType;
        public Vehicle(string vehicleType)
        {
            this.vehicleType = vehicleType;
            // Setting the initial behavior
            this.behavior = new InitialBehavior();
        }
        /*
         * It's your choice. You may prefer to use a setter
         * method instead of using a constructor.
         * You can call this method whenever we want
         * to change the "vehicle behavior" on the fly.
         */
        public void SetVehicleBehavior(VehicleBehavior behavior)
        {
            this.behavior = behavior;
        }
        /*
        This method will help us to delegate the behavior to
the object referenced by vehicle.You do not know about the object type, but you simply know that this object can tell something about it, i.e. "AboutMe()" method
        */
        public void DisplayAboutMe()
        {
            behavior.AboutMe(vehicleType);
        }
    }

You can see, inside the constructor, I set the initial behavior, which can be altered later using the SetVehicleBehavior(...) method . DisplayAboutMe() delegates the task to a particular object.

Class Diagram

Figure 15-1 shows the important parts of the class diagram.
../images/463942_2_En_15_Chapter/463942_2_En_15_Fig1_HTML.jpg
Figure 15-1

Class diagram

Solution Explorer View

Figure 15-2 shows the high-level structure of the program.
../images/463942_2_En_15_Chapter/463942_2_En_15_Fig2_HTML.jpg
Figure 15-2

Solution Explorer view

Demonstration

Here’s the implementation.
using System;
namespace StrategyPattern
{
    /// <summary>
    /// Abstract Behavior
    /// </summary>
    public abstract class VehicleBehavior
    {
        public abstract void AboutMe(string vehicle);
    }
    /// <summary>
    /// Floating capability
    /// </summary>
    class FloatBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} can float now.");
        }
    }
    /// <summary>
    /// Flying capability
    /// </summary>
    class FlyBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} can fly now.");
        }
    }
    /// <summary>
    /// Initial behavior.Cannot do anything special.
    /// </summary>
    class InitialBehavior : VehicleBehavior
    {
        public override void AboutMe(string vehicle)
        {
            Console.WriteLine($"My {vehicle} is just born.It cannot do anything special.");
        }
    }
    /// <summary>
    /// Context class-Vehicle
    /// </summary>
    public class Vehicle
    {
        VehicleBehavior behavior;
        string vehicleType;
        public Vehicle(string vehicleType)
        {
            this.vehicleType = vehicleType;
            //Setting the initial behavior
            this.behavior = new InitialBehavior();
        }
        /*
         * It's your choice. You may prefer to use a setter
         * method instead of using a constructor.
         * You can call this method whenever we want
         * to change the "vehicle behavior" on the fly.
         */
        public void SetVehicleBehavior(VehicleBehavior behavior)
        {
            this.behavior = behavior;
        }
        /*
        This method will help us to delegate the behavior to
the object referenced by vehicle.You do not know about the object type, but you simply know that this object can tell something about it, i.e. "AboutMe()" method
        */
        public void DisplayAboutMe()
        {
            behavior.AboutMe(vehicleType);
        }
    }
    /// <summary>
    /// Client code
    /// </summary>
    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Strategy Pattern Demo.*** ");
            Vehicle context = new Vehicle("Aeroplane");
            context.DisplayAboutMe();
            Console.WriteLine("Setting flying capability to vehicle.");
            context.SetVehicleBehavior(new FlyBehavior());
            context.DisplayAboutMe();
            Console.WriteLine("Changing the vehicle behavior again.");
            context.SetVehicleBehavior(new FloatBehavior());
            context.DisplayAboutMe();
            Console.ReadKey();
        }
    }
}

Output

Here’s the output.
***Strategy Pattern Demo.***
My Aeroplane is just born.It cannot do anything special.
Setting flying capability to vehicle.
My Aeroplane can fly now.
Changing the vehicle behavior again.
My Aeroplane can float now.

Q&A Session

15.1 It appears to me that you are complicating everything by focusing on changing behaviors. Also, I do not understand why I need the Context class at all. You could simply use the inheritance mechanism and proceed. Can you please address these concerns?

If a behavior is common for all subtypes, it’s okay to use inheritance, for example, you can make an abstract class and put the common behavior into it so that all child classes get the common behavior. But the real power of strategy comes into picture when the behaviors can vary across the objects, and maintaining them using inheritance is difficult.

For example, let’s say that you start with different behaviors, and you place them in an abstract class as follows.
    public abstract class Vehicle
    {
        public abstract void AboutMe();
        public abstract void FloatBehavior();
        public abstract void FlyBehavior();
        public virtual void DefaultJob()
        {
            Console.WriteLine("By default, I float.");
        }
    }
Now let’s say that Boat and Aeroplane are two concrete classes that inherit from it. You know that a Boat object should not fly, so inside the Boat class, you can simply override FlyBehavior as follows.
    public override void FlyBehavior()
    {
        throw new NotImplementedException();
    }
Similarly, an Aeroplane object should not float in water (in a normal situation). So, inside the Aeroplane class, you may override FloatBehavior as follows.
    public override void FloatBehavior()
    {
     throw new NotImplementedException();
    }

Now consider when you have lots of changing behaviors across objects like these. This kind of maintenance can be overhead.

Apart from this, let’s consider a special vehicle that has specialized features. If you simply put those special features in the abstract class, all other vehicle object inherits those and need to implement those. But it is not over yet. Further, assume that there is a constraint on the Boat class, which simply says that it cannot have any such special behavior. Now you encounter a deadlock situation. If you implement this special method, you are violating the constraint. If you do not implement it, the system architecture breaks because the language construct requires you to implement the behavior. (Or, you need to mark the class with the abstract keyword, but at the same time, remember that you cannot create an instance from an abstract class.)

To overcome this, I can create a separate inheritance hierarchy with an interface to hold all the specialized features, and my classes can implement the interface if needed. But again, it may solve the problem partially because the interface may contain multiple methods, and your class may need to implement only one of them. In the end, in any of these cases, the overall maintenance becomes tough. Apart from this, the special behaviors may change, and in that case, you need to track down all the classes that implement these behaviors.

In a situation like this, the context class acts as a savior. For example, for the Boat class object, the client does not set the fly behavior, or for Aeroplane class objects, the client does not set the float behavior; he simply knows which behavior is expected from the particular vehicle. So, if you want, you can guard against a situation in which a client mistakenly sets an incorrect behavior to a vehicle.

To simplify this, the context class holds a reference variable for the changing behavior and delegates the task to the appropriate behavior class. This is why you see the following segment in our Vehicle context class.
    public class Vehicle
    {
        VehicleBehavior behavior;
        //Some other code
        /*
         * It's your choice. You may prefer to use a setter
         * method instead of using a constructor.
         * You can call this method whenever we want
         * to change the "vehicle behavior" on the fly.
         */
        public void SetVehicleBehavior(VehicleBehavior behavior)
        {
            this.behavior = behavior;
        }
       //Some other code
    }

A “has-a” relationship fits better than an “is-a” relationship for this example, and it is one of the primary reasons that most of the design patterns encourage composition over inheritance.

15.2 What are the key advantages of using a Strategy design pattern?

Here are some of the key advantages.
  • This design pattern makes your classes independent from algorithms. Here a class delegates the algorithms to the strategy object (that encapsulates the algorithm) dynamically at runtime. So, the choice of algorithms is not bound at compile time.

  • It’s easier to maintain your codebase.

  • It’s easily extendable.

You can refer to the answer in Q&A 15.1 in this context.

15.3 What are the key challenges associated with a Strategy design pattern?

The disadvantages can be summarized as follows.
  • The addition of context classes causes more objects to exits in your application.

  • Users of the application must be aware of different strategies; otherwise, the output may surprise them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.174.239