Tuesday, November 22, 2011

The Strategy Pattern

While they can be abused in various ways, design patterns are an essential subject to become familiar with, so I'm going to dedicate the next series of articles to design patterns.

First up is the strategy pattern. This pattern is very useful for changing a class's behavior at runtime.
Let's say we have a class that implements some method. The method works great but now we'd like to inherit from that class. The method will be inherited as well and will perform the same function. Any additional class we inherit from the base class will also come with this method. But what if one of our subclass' doesn't implement that method in exactly the same way? Well we could always make the method virtual and override it so it does something else but what if we want the class to sometimes perform the method normally and sometimes do it a little different? Here we would run into some problems.

Let's take a simple example. Consider a Car class that implements a Honk() method. Every subclass of Car will have it's own specific honk sound. This is all very well and good, but what if the owner wants to change the honk sound? What if the owner is a bit eccentric and decides they want a different sound for each day of the week? This quickly becomes a maintenance nightmare.

A way around this is to create an interface or an abstract class containing a method stub for Honk behavior. The interface is meant to encapsulate honking. Any concrete strategy class that inherits from the honking interface can implement its Honk method however it wants. The Car class, in turn, will contain a reference to a honking strategy object. When the Car calls its honking method, the strategy's method is called.
This allows for some flexible car honking behavior because now we can write methods to change one Honk interface for another. During runtime a car object can change how it honks!

These abstract behavior classes are called strategies.

Before we get into the code, let's take a quick look at the UML diagram for this pattern. You can skip over this image if you're not familiar with UML as it isn't necessary to follow the article. It can also serve as a handy reference for later.


Our Honk strategy would look something like this:
public interface IHonk
{
    void Honk();
}

Now we can inherit from this interface to make specific honks. We'll make a few here now, a NormalHonk, BrokenHonk, and a GodFatherThemeHonk.
public class NormalHonk : IHonk
{
    public void Honk()
    {
        Console.WriteLine("Honk!");
    }
}

public class BrokenHonk : IHonk
{
    public void Honk()
    {
        Console.WriteLine("fffffffff......");
    }
}

public class GodFatherThemeHonk : IHonk
{
    public void Honk()
    {
        Console.WriteLine("**mandolin music**");
    }
}

Our car class needs a reference to an IHonk object. The car's PerformHonk() method will call the Honk() method from the strategies reference.
public class Car
{
    IHonk _honkStrategy = new NormalHonk();
    
    public void PerformHonk()
    {
        _honkStrategy.Honk();
    }

    public void SetHonkStrategy(IHonk honkStrategy)
    {
        _honkStrategy = honkStrategy;
    }
}

Each car object is initialized with a NormalHonk but we can change this at any time.
Let's create a few cars and see how this works in action
class Program
{
    static void Main(string[] args)
    {
        //Here we initialize a new car, leaving it's honk strategy
        //at its default.
        Car _honda = new Car();
        
        //Here we'll make a car and set it's honk to something else.
        Car _elCamino = new Car();
        _elCamino.SetHonkStrategy(new GodFatherThemeHonk());

        //Each car has it's own honking behavior...
        _honda.Honk();
        _elCamino.Honk();

        //The Honda's horn breaks!
        _honda.SetHonkStrategy(new BrokenHonk());
        
        _honda.Honk();

        Console.ReadLine();
    }
}

We would also, ideally, make the Car class abstract and inherit from it.
public abstract class Car
{
    IHonk _honkStrategy;
    
    public void PerformHonk()
    {
        _honkStrategy.Honk();
    }

    public void SetHonkStrategy(IHonk honkStrategy)
    {
        _honkStrategy = honkStrategy;
    }
}

public class Honda : Car
{
    public Honda()
    {
        SetHonkStrategy(new NormalHonk());
    }
}

public class ElCamino : Car
{
    public ElCamino()
    {
        SetHonkStrategy(new GodFatherThemeHonk());
    }
}
And our Program class becomes simpler.
class Program
{
    static void Main(string[] args)
    {
        Car _honda = new Honda();
        
        Car _elCamino = new ElCamino();
        
        //Each car has it's own honking behavior...
        _honda.Honk();
        _elCamino.Honk();

        //The Honda's horn breaks!
        _honda.SetHonkStrategy(new BrokenHonk());
        
        //The El Camino's owner wants a normal honk.
        _elCamino.SetHonkStrategy(new NormalHonk());
        
        _honda.Honk();
        _elCamino.Honk();

        Console.ReadLine();
    }
}
And that's the Strategy Pattern. It's very simple but extremely effective at changing behavior during runtime.
Thanks for reading and I hope this was an enlightening look at the Strategy Pattern!

No comments :

Post a Comment