Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Strategy Design Pattern - Explained With A Real World Example (C#)

4.93/5 (10 votes)
14 Aug 2015CPOL6 min read 37.6K  
Strategy Design Pattern - Explained with a real world example (C#)

Strategy Pattern is a Behavioral pattern.

In theory, the Strategy Pattern means:

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

How do we elaborate it? Or what can be a practical example of this?

Let us consider, we are creating a basic simulator for the mobile handset. A user (client application) selects a handset, and should be able to function whatever is supported in the handset.

Image 1

As we all know, there can be different types of mobile devices. Old (and cheapest) devices just support Call and SMS functionalities, and there is a range of smart phones/ new handsets which supports cool hardware and features like, rear camera (i.e., main camera), or front camera (i.e., the camera to take your selfie!). There are a number of features in the smartphones (much more than camera), but to make the article a bit simple, we will only focus on the following four features:

  • Phone Call
  • SMS
  • Front Camera
  • Rear Camera

We are taking an example of three different handsets:

  1. Nokia 5510 - having only basic features (i.e., phone call and SMS)
  2. Nokia Lumia 920 - having tons of cool features (but for our case, it's only Front Camera and Rear Camera that matters!)
  3. Nokia 6600 - having only rear camera (sorry, but there was no facility to take a selfie with this 10 year old handsets!)

In the Object-oriented world, these three classes are called as "concrete implementations". So in future, we should be able to add a number of handsets, and so a concrete class for each of them.

Step 1: Identify the Behavior(s) Which Are Common Across All the Clients

This means, there is a need of an abstract class containing the behavior which is common across all devices. This will be a base class for all the handsets.

Remember, this abstract class is the heart of Strategy pattern. This means it will contain (and is capable to plug-in) everything to create different handsets and attach various behaviors to them. This class is referred to as "Strategy".

C#
public abstract class Handset
{
    public abstract string Display();

    public string MakeACall(string destinationNumber)
    {
        return String.Format("Calling {0}", destinationNumber);
    }

    public string SendSms(string destinationNumber)
    {
        return String.Format("Sending SMS to {0}", destinationNumber);
    }
}

Display() is an abstract method because all different handsets will have their own implementation of how they should appear.

MakeACall() and SendSms() are concrete methods because all devices should make a call or send an SMS in the same manner.

Step 2: Do Something For What Varies!

In our example, we have Camera features which are varying in different devices. That means, a few handsets support both front and rear cameras, whereas a few support only rear camera, and there are those old phones which don't have a camera at all.

Let's create an interface which any type of camera can inherit from:

C#
public interface IPhotographyDevice
{
    string TakeSnap(string location);

    string RecordVideo(string location);
}

And, then the two classes implementing this interface, each class for each type of camera (i.e. rear camera, and front camera).

A class implementing the interface for RearCamera is as follows:

C#
public class RearCamera : IPhotographyDevice
{
    public int megaPixels { get; set; }

    public RearCamera(int supportedMegaPixels)
    {
        megaPixels = supportedMegaPixels;
    }

    public string TakeSnap(string location)
    {
        return String.Format("Taking Photograph on {0} MP main camera", megaPixels.ToString());
    }

    public string RecordVideo(string location)
    {
        return String.Format("Recording Video on {0} MP main camera", megaPixels.ToString());
    }
}

So, you can see that we have encapsulated the photography features in their concrete implementations (i.e., FrontCamera and RearCamera).

Step 3: Integrating the Behavior(s) Which Vary

The abstract class should now delegate the photography related behavior to IPhotography interface.

For that, we need to inform the Handset class about IPhotography interface, so all the concrete classes of handsets inheriting from Handset class can use photography features, if they need to support them (i.e., taking photos, and record videos through Front and/ or Rear cameras).

Handset class revisited to add an instance variable:

C#
    public abstract class Handset
    {
........
        public IPhotographyDevice photographyDevice;
    }

Step 4: Implement the Methods for the New Behavior

Handset class revisited to add TakeSnap and RecordVideo methods which call relevant methods from concrete photography classes, if supported by the handset.

C#
 public abstract class Handset
    {
....
        public string TakeSnap(string location)
        {
            if (photographyDevice != null)
            {
                return photographyDevice.TakeSnap(location);
            }
            else
            {
                return @"Sorry! this phone doesn't support photography";
            }
        }

        public string RecordVideo(string location)
        {
            if (photographyDevice != null)
            {
                return photographyDevice.RecordVideo(location);
            }
            else
            {
                return @"Sorry! this phone doesn't support videography";
            }
        }
    }

Step 5: Setting Behavior Dynamically

Our application allows users to change the camera run-time. Notice the "Activate Front Camera" and "Activate Rear Camera" buttons in the screenshot.

Here is the beauty of Strategy pattern wherein you can change the behavior dynamically (run-time polymorphism, as they say!)

Handset class revisited to add a setter method (i.e., a method that sets an instance variable - in our case, it is "photographyDevice".

C#
public abstract class Handset
    {
.......
        public void SetPhotographyDevice(IPhotographyDevice newPhotographyDevice)
        {
            photographyDevice = newPhotographyDevice;
        }
........
    }

Step 6: Implement Concrete Classes from the Strategy

Now we have our Strategy (i.e. Handset abstract class) is ready, it's time to create concrete classes.

C#
public class Nokia5510: Handset
    {
        public override string Display()
        {
            return "This is a very basic model from Nokia with no camera whatsoever!!";
        }
    }

This particular device doesn't support any sort of camera, and so we don't need to set the instance of "IPhotography" variable.

When your application will try to execute any photography related methods (i.e., TakeSnap or RecordVideo), then it will say, the phone doesn't support these features).

C#
public class NokiaLumia920 : Handset
    {
        private readonly int rearCameraMPs = 12;
        private readonly int frontCameraMPs = 5;        

        public NokiaLumia920()
        {
            photographyDevice = new MainCamera(mainCameraMPs);
        }

        public void LoadFrontCamera()
        {
            SetPhotographyDevice(new FrontCamera(frontCameraMPs));
        }

        public void LoadRearCamera()
        {
            SetPhotographyDevice(new RearCamera(rearCameraMPs));
        }
        
        public override string Display()
        {
            return "Nokia Lumia 920... A full-featured phone from Nokia";
        }
    } 

This device supports both front and rear cameras, and the client application can switch the cameras at run-time, by calling "LoadFrontCamera" and "LoadRearCamera" methods which internally calls a setter property to change the behavior. That means, we initially attach the behavior (through constructor), and then change it.

C#
public class Nokia6600 : Handset
    {
        private readonly int rearCameraMPs = 12;

        public Nokia6600()
        {
            photographyDevice = new RearCamera(mainCameraMPs);
        }

        public void LoadFrontCamera()
        {
            //Front-camera are not supported in this device
            SetPhotographyDevice(null);
        }

        public void LoadRearCamera()
        {
            SetPhotographyDevice(new RearCamera(mainCameraMPs));
        }

        public override string Display()
        {
            return "Nokia Lumia 6600... 
            A Nokia phone with a lots of features and a high-resolution camera";
        }
    }

This device supports only rear camera, and so when your application tries to attach front camera, the instance is assigned to a null value. A client application can again start the rear camera by invoking "LoadRearCamera".

Step 7: Code the Simulator

By now, we have the "Strategy" (i.e. our Handset abstract class), interfaces and their concrete implementations, and concrete handset classes are ready. Now, it's just a matter of instantiating the handset classes (based on the selected handset in drop-down) and invoking methods in various events of simulator.

Not being lazy, but I am leaving this to the readers to implement this simulator. Feel free to comment in case you face any difficulties.

Summary

To summarize and conclude, we have used "Strategy design pattern" in our Mobile Handset simulator application, and following are the benefits:

  • Behaviors can be added at run-time
  • Behaviors can be switched at run-time
  • Any new implementation of a behavior will not require any change in the "Strategy" class.
  • There is a single class (called as Strategy) that contains everything which is required by the clients. (abstract methods, concrete methods, instance variables of Interface type for the behavior and setter methods to change the behavior at run-time)
  • If there is a need to support any new device in the simulator, and if that device has possible features within call, sms, and camera, then it's just a matter of adding a new concrete class for the handset as we have done.
  • If the new handsets are supporting a different type of camera (may be, a "spy camera"), then you will just need to create a new concrete type from "IPhotographyDevice", and you should be able to use the "Spy camera" in your simulator straight-away. And similarly, can switch between - front, rear and spy cameras similar to what we did in our example.
  • If the new handsets are supporting some new behaviors, then a new interface for the behavior will need to be added, and so it's concrete application (as per step 2). After that, the "Strategy" class will need to integrate that behavior (steps - 3,4,5). And phew, you are ready to use this new behavior.

Hope this article helped you in understanding Strategy pattern. Feel free to ask any questions, or provide feedback!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)