Skip to main content

♟️ Strategy

Strategy Pattern is a design pattern that separates algorithms or methods from an object, allowing them to be swapped in and out at runtime depending on the situation. It provides flexibility and modularity without having to modify the main object’s codebase.

🌍 Real-World Analogy

Various strategies for travelling from Tokyo to Okayama

Various strategies for travelling from Tokyo to Okayama (Image by author, illustrations by Takashi Mifune under free use)

Imagine that you are on a long-awaited trip to Japan. Suddenly, you make changes to your itinerary and need to go from Tokyo to Okayama.

You have a few options, each with their own benefits and drawbacks. You could cycle across the countryside, take an overnight bus, hop on the world-famous Shinkansen, or if you’re pressed for time, catch a flight.

Just like in software engineering, in this situation you have a number of different strategies to get from point A to point B. You need to carefully consider the external factors, such as time, budget and personal preferences, before making a decision.

Each strategy has its own advantages and disadvantages, just like each algorithm in programming.

For example, cycling might be the cheapest and most immersive way to experience Japan, but it could be time-consuming. On the other hand, catching a Shinkansen might be fast and comfortable, but could be expensive. Ultimately, choosing the right strategy depends on your needs and circumstances.

🤔 Problem

You decided to create software for vacation planning. The app’s primary feature is a map with which users can select and put the stations of their itinerary.

The most requested feature was automatic transport planning. A user should be able to automatically calculate the route between two stations in their itinerary.

In the beginning, the app was only able to calculate routes based on roads. Car-based travelers were happy, but some destinations, especially Japan, are known for rail-based travel. So, you added an option for calculating routes for public transport.

Bad Example: Setting yourself up for misery through bloated classes

Bad Example: Setting yourself up for misery through bloated classes

Later, route building for pedestrians and route building for cyclists were required. Later, people asked for route building in remote areas.

The team became increasingly frustrated, as the RouteBuilder class grew and merge confliecs became more frequent.

💡 Solution

The strategy pattern is about having a class that solves one issue in a lot of different ways. These are different algorithms extracted into separate classes.

The main class, usually called Context, is controlling the various available strategies. Thus, it needs to have them stored in variables and needs to be aware of their differences.

Concept of using Strategy Pattern, created for routing

Concept of using Strategy Pattern, created for routing

The Context is working with the various strategies and accesses them through a joint interface.

🏗️ Structure

Example structure

Example structure

  1. Context maintains the reference to one specific Strategy Implementation and communicates with it through it’s interface
  2. Strategy interface is being implemented by all individual strategies
  3. Strategy Implementations implement different types of an algorithmn which is consumed by Context.
  4. Client is deciding for a specific Strategy Implementation and is passing it into the Context. The Context exposes a function to replace the Strategy during runtime.

Refactor from Bad to Good

Graphical overview about how to refactor

Graphical overview about how to refactor

  1. Identify an algorithm that is prone to being changed frequently. Sometimes, you can identify such cases through their usage of massive conditionals and similar method names.
  2. Declare a Strategy Interface which will be the same for all variants of the algorithm.
  3. Extract the single algorithms into their own classes. They need to implement the Strategy Interface
  4. Create a Context and store the Strategy Implementation there.
  5. Use the Client to associate each scenario with its respective Strategy Implementation.
using System;
using System.Collections.Generic;

namespace RoutingExample
{
public interface IRouteStrategy
{
List<DirectionPoint> GetDirections(string startPoint, string endPoint);
}

public class DrivingRouteStrategy : IRouteStrategy
{
public List<DirectionPoint> GetDirections(string startPoint, string endPoint)
{
// Get driving directions and return as a list of steps
return GetDrivingDirections(startPoint, endPoint);
}

private List<DirectionPoint> GetFlyingDirections(string startPoint, string endPoint)
{
}

}

public class FlyingRouteStrategy : IRouteStrategy
{
public List<DirectionPoint> GetDirections(string startPoint, string endPoint)
{
// Get flying directions and return as a list of steps
return GetFlyingDirections(startPoint, endPoint);
}

private List<DirectionPoint> GetFlyingDirections(string startPoint, string endPoint)
{
}
}

public class RoutingContext
{
private IRouteStrategy _strategy;

public RoutingContext(IRouteStrategy strategy)
{
_strategy= strategy;
}

public List<DirectionPoint> RouteTravel(string startPoint, string endPoint)
{
return _strategy.GetDirections(startPoint, endPoint);
}
}

class Program
{
static void Main(string[] args)
{
IRouteStrategy _Strategy;
String _TravelMode = "Car";

switch(_TravelMode) {
case "Car":
_Strategy = new DrivingRouteStrategy();
break;
case "Plane":
_Strategy = new FlyingRouteStrategy();
break;
}

//decide on which strategy to use. in this exmaple, i just decide to use the first one
RoutingContext routingContext = new RoutingContext(_Strategy);

// Example usage:
string startPoint = "Tokyo";
string endPoint = "Okayama";

List<DirectionPoint> directions = routingContext.GetDirections(startPoint, endPoint);
foreach (DirectionPoint step in directions)
{
Console.WriteLine(step);
}
}
}
}

Pros & Cons

✅ Pros

Swapability. Interchange algorithms within an object while the program is running

Isolation. Separate the implementation details of an algorithm from the code that utilizes it.

Replace inheritance with composition. Composition can help you to create more flexible and reusable code.

Open/Closed Principle. Introduce new strategies without changing the underlying code.

❌ Cons

Might overcomplicate things. Sometimes, an easier approach might be suitable.

🔗 Relation with other patterns

  • Bridge, State, Strategy, and Adapter, are all based on composition and delegate work to other objects but solve different problems
  • Decorator changes the appearance of an object while Strategy changes the inter workings
  • Template Method, is based on inheritance while Strategy is based on composition; Template Method is static while Strategy is dynamic at the object level and lets you switch behaviors at runtime.