Strategy Pattern

Intro

Welcome back to another design pattern post. In this post, we will deep dive into a pattern called strategy pattern

 

We will start with a real-life analogy of the pattern. Then we will analyse and simplify the principle definition and finally will demonstrate a code for it.

 

As usual, get your coffee ☕️   and let’s do this.

strategy pattern better-dev.io

Real Life Example

In a football match, a team will not just play randomly. Instead,  They will play based on a predefined strategy by the coach. 

strategy pattern example

 Noticeably, we can find many instances in which a team changes its strategy to cope with the match. For example, the coach might change a player, instruct the team to push more and so on.  

 

From this example, we can conclude that a strategy is a way of executing a set of instructions, to achieve a required output. In addition, executing the instructions are dependent upon the required output. 

 

Now let’s go to the definition. But before that, let’s take a meme break. 

strategy pattern

Principle Definition

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

The Strategy Pattern meme

The definition might sound intimidating and meant for mathematicians. But, don’t worry it is not the case.  In fact, let’s simplify the definition with a sprinkle of software developer perspectives on it. 

The strategy pattern enables interchanging between a logic performed on a given input.

By now you are like, give me the code already. Therefore, here it comes.

Code Example

strategy pattern example

If we look at services like Uber, Kareem, BiTaksi or any similar transportation service.

We will notice that the price of the trip is estimated based on the distance from A to B and the type of car and other hidden fees that we don’t normally see in the UI. 

 

We will take the above UI and write the code for it using a strategy pattern. 

 

Note: dart programming language is used for this example. However, you can follow along using any object programming language. 

Code Structure 

to start with lets have an overview of the code structure. 

strategy pattern uml

The heart of the code is the ITripEstimation interface which defines the input and the output of the price estimation. 

 

CabinTripEstimation, RegularTripEstimation and TurquoiseTripEstimation are three implementations of the ITripEstimation interface. Each will result in a different result. 

 

Finally, TripPriceService is basically a class that accepts one of the implementations and gets the price by invoking the method in the ITripEstimation interface.

 

Let’s see it in action.

Step 1: Data Classes

class TripDetails {
  double fromLat;
  double fromLong;
  double toLat;
  double toLong;

  TripDetails(this.fromLat, this.fromLong, this.toLat, this.toLong);
}

enum VehicleType {
  regular,
  cabin,
  turquoise
}

class VehicleDetails {

  final String name;
  final num baseFee;
  final VehicleType vehicleType;
  
  VehicleDetails(this.name, this.baseFee,this.vehicleType);
}

We will need a couple of classes to hold the vehicle and trip details data.

Step 2: Defining the Interface

abstract class ITripEstimation {
   double EstimateTripPrice(TripDetails tripDetails);
}

Step 3: Writing an implementation

class CabinTripEstimation implements ITripEstimation {
  VehicleDetails _vehicleDetails;

  CabinTripEstimation(this._vehicleDetails);

  @override
  double EstimateTripPrice(TripDetails tripDetails) {
    // mimics estimation algorithm
    return _vehicleDetails.baseFee * Random().nextDouble() * 1.4;
  }
}

To keep the post short, I will only write one implementation. But you can view the full documented source code in the link provided at the bottom of the post.

Step 4: Writing the Service

class TripPriceService {

  late ITripEstimation vehicle;

  setEstimator(ITripEstimation vehicle) {
    this.vehicle = vehicle;
  }

  double getTripPrice(TripDetails tripDetails){
    return this.vehicle.EstimateTripPrice(tripDetails);
  }
}

This class is responsible for holding an instance of implementation, and invoking the method of the interface.

Step 5: Calling the Service

void main(List<String> arguments) {
  // setting the vehicleType to mimic a user selection
  var vehicleType = VehicleType.turquoise;
  // mimics vehicleDetails 
  var vehicleDetails = VehicleDetails("toyota", 1.2, vehicleType);
  // mimics trip detail from point A to B 
  var tripDetails = TripDetails(0.2, 0.5, 0.4, 0.4);

  // new trip service
  var tripPriceService = TripPriceService();

  // setting the calclation strategy 
  // based on vehicleType
  switch (vehicleType) {
    case VehicleType.cabin:
      tripPriceService.setEstimator(CabinTripEstimation(vehicleDetails));
      break;
    case VehicleType.regular:
      tripPriceService.setEstimator(RegularTripEstimation(vehicleDetails));
      break;
    case VehicleType.turquoise:
      tripPriceService.setEstimator(TurquoiseTripEstimation(vehicleDetails));
      break;
  }

 // getting the trip price
  var tripPrice = tripPriceService.getTripPrice(tripDetails).toStringAsFixed(2);

  print(tripPrice);
}

This is the entry point of this implementation, it is responsible for getting the required data, selecting the appropriate implementation and print the price.

Let’s go through the code together. 

  • The lines from 3 to 7 mimic getting the data from the UI. 
  • Line 10 getting a new instance of TripPriceService()
  • The lines from 14 to 23 set the price estimation implementation based on vehicle type.
  • Line 27 getting the trip price and print it in line 29.

When to use this pattern?

The pattern is easy to understand and implement.  However, the million-dollar question is when to use it? and when not?

 

The best use case for this pattern is when having three or more implementations in mind and you want to switch between them at runtime. In addition, it is better to use it when the likelihood of adding more implementations is high. 

 

On the other hand, the worst-case scenario is to use this pattern for one or two fixed implementations. Doing so is over-engineering and unnecessary.

Conclusion

The strategy pattern is one of the strategies that provide a great level of decoupling, separation of concerns and flexibility of change. However, it must be used in the appropriate case to harness it is full capabilities.

Support Ideas

If you liked the content and thought of supporting the creator, below are some ideas to do so.

  • your engagement in the comment will help giving more value to the content.
  • spreading the content in social media will help attract more readers and google ranking
Share on facebook
Share on twitter
Share on linkedin
Share on whatsapp
Share on email
You Can

Buy me a Coffee

  • also consider subscribing to the newsletter list

3 Comments

  1. […] from Pester 5 code coverage (Donovan Brown) FIRST: an idea that ran away from home (Tim Ottinger) Strategy Pattern (Anwar Al Jahwari) Custom-Build Your Security With Multiple Authentication Policies (Kenza Moller) […]

  2. very clearly and simplistly explanation,
    Thanks

Leave a Reply

Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124