Basics of Design Patterns — Strategy♟️

Martin Jurran
Software Design Patterns
6 min readJun 13, 2024

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

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

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

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

Structure 🏗️

  1. The Context maintains the reference to one specific Strategy Implementation and communicates with it throigh it’s interface
  2. The Strategy interface is being implemented by all individual strategies
  3. Strategy Implementations implement different types of an algorithmn which is consumed by Context.
  4. The 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
  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 and 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.

Attachments

Mermaid Diagram

///Diagram with bad design example:
classDiagram
class RouteBuilder{
+buildCar()
+buildPublicTransport()
+buildBike()
+buildPedestrian()
+buildOffRoad()
}
note for RouteBuilder "Bloated class with way too many algorithms.\n This sets you up for merge conflicts and other issues later."
//Diagram with Strategy Concept
classDiagram
class NavigationHandler {
-StrategyRouting
+buildRoute(A,B)
}
class StrategyRouting {
<<interface>>
+buildRoute(A, B)
}
class StrategyCar{
+buildRoute(A, B)
}
class PublicTransport{
+buildRoute(A, B)
}
class StrategyBike{
+buildRoute(A, B)
}
class StrategyPedestrian{
+buildRoute(A, B)
}
class StrategyOffRoad{
+buildRoute(A, B)
}
StrategyRouting <|-- StrategyCar
StrategyRouting <|-- PublicTransport
StrategyRouting <|-- StrategyBike
StrategyRouting <|-- StrategyPedestrian
StrategyRouting <|-- StrategyOffRoad
StrategyRouting --* NavigationHandler
//Strategy Concept Structure
classDiagram
class Context{
-Strategy
+SetStrategy(strategy)
+Run()
}

class Strategy{
<<interface>>
+Execute(args)
}
class ImplementsStrategy{
+Execute(args)
}
class Client{

}
Strategy --* Context
ImplementsStrategy-->Strategy
Client-->Context
Client..>ImplementsStrategy

About this series

I noticed personally, that design patterns still remain a mistery to a lot of software engineers, even though these concepts are nothing new and have been around for decades.

I want to solve this problem with this article series to help other software engineers grow.

Feel free to comment in case you find mistakes or want to add your own perspective!

(Photo by the author, Illustrations by Takashi Mifune under free use)

--

--

Martin Jurran
Software Design Patterns

Personal blog of a Software Engineer | Azure DevOps, C#/.NET, JavaScript