Strategy Pattern is a behavioral design pattern that enables selecting an algorithm’s behavior at runtime.

It’s widely used and very flexible, perfect for cases where you want to avoid complex if-else or switch statements.

Use cases

  • You want to replace conditionals (if-else or switch) with polymorphism.
  • You need to switch behavior at runtime or make it configurable. Example: User selecting payment option
  • You want to define multiple ways of doing something (e.g., sorting, compression, payment).

Core Concepts

The pattern consists of three main components:

  1. Strategy: An interface or abstract class that defines a common method for all concrete strategies. The context uses this interface to call the algorithm.

  2. Concrete Strategy: The classes that implement the Strategy interface. Each class provides a specific implementation of the algorithm.

  3. Context: The class that holds a reference to a Strategy object. It uses the strategy’s method to perform a certain action. The context is unaware of the concrete strategy being used, it only interacts with the Strategy interface.

Example

Payment Strategy

  1. Strategy Interface
public interface PaymentStrategy {
    void pay(double amount);
}
  1. Concrete Strategies
public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using Credit Card.");
    }
}

public class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using PayPal.");
    }
}

public class CryptoPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using Cryptocurrency.");
    }
}
  1. Context Class
public class PaymentContext {
    private PaymentStrategy strategy;

    public PaymentContext() {}

    // optional - for example you want to provide default strategy 
    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy
    }

    // Strategy can be changed at runtime
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void payAmount(double amount) {
        if (strategy == null) {
            throw new IllegalStateException("Payment strategy not set.");
        }
        strategy.pay(amount);
    }
}

Kotlin implementation (functional style strategy)

In kotlin, you can implement Strategy patten using OOP approach and functional style approach. OOP way is similar to how we implemented in java.

Function style strategy

typealias PaymentStrategy = (Double) -> Unit

class PaymentContext(private var strategy: PaymentStrategy) {
    fun setStrategy(strategy: PaymentStrategy) {
        this.strategy = strategy
    }

    fun pay(amount: Double) = strategy(amount)
}
fun main() {
    val creditCardPayment: PaymentStrategy = { amount ->
        println("Paid \$$amount using Credit Card.")
    }

    val paypalPayment: PaymentStrategy = { amount ->
        println("Paid \$$amount using PayPal.")
    }

    val cryptoPayment: PaymentStrategy = { amount ->
        println("Paid \$$amount using Cryptocurrency.")
    }

    val context = PaymentContext(creditCardPayment)
    context.setStrategy(paypalPayment)
    context.pay(100.0)
}

Real world use-cases

  • Comparator<T> for sorting in Java

  • ThreadPoolExecutor with RejectedExecutionHandler

  • Logging frameworks (ConsoleLogger, FileLogger, RemoteLogger)

Drawbacks

  • The pattern can lead to explosion of classes and objects. For every new algorithm, you need to create a new concrete strategy class.
  • The client (or context) needs to be aware of the different available strategies to choose and instantiate the correct one.
  • While the pattern simplifies the context by removing conditional logic, it can introduce more complexity overall.
  • For very simple, straightforward algorithms, using a strategy interface and multiple classes can be overkill.

Parking Lot Problem

In this article, let’s take it a step further and solve the Parking Lot Problem using the Strategy Pattern.

Problem: Let’s say we want to design a Parking Lot system. It should assign an available parking spot to any vehicle that wants to park. The way spots are assigned can vary depending on different rules or criteria (eg: vehicle size, vip vehicle, ..). New requirements might also change how the system chooses a parking spot.

As you can see, there can be multiple ways to assign a parking spot. For example, larger vehicles like trucks may require dedicated spaces that differ from those used by regular cars. Also, we may get new requirements or there may be changes in the requirements. Using a traditional approach with multiple if-else or switch statements can quickly become hard to manage and scale. A better solution for this scenario is the Strategy Pattern, which allows us to define different spot assignment strategies based on specific criteria, making the system more flexible and maintainable.

Lets first define the strategy

  1. Define Strategy
// implementations will be like Car, Truck, SUV, Bike ..
interface Vehicle {
  String name();
}

interface ParkingStrategy {
  int findAvailableSlot(Vehicle vehicle);
}
  1. Concrete Strategies

Lets now define the Concrete Strategies. Let say we have 3 strategies:

  • ClosestSpotParkingStrategy: Assign the nearest vacant spot to the entrance to reduce the time taken for vehicles to park.
  • TypeBasedParkingStrategy: Reserve specific spots for different types of vehicles (e.g., compact cars, oversized vehicles).
  • PriorityParkingStrategy: Implement priority parking for certain vehicles based on factors like membership status, VIP status, or special needs.
class ClosestSpotParkingStrategy implements ParkingStrategy {

  @Override
  public int findAvailableSlot(Vehicle vehicle) {
    System.out.println("Using closest spot parking strategy..");
    // fake spot number
    return 17; // implement closest spot parking logic
  }
}

class TypeBasedParkingStrategy implements ParkingStrategy {

  @Override
  public int findAvailableSlot(Vehicle vehicle) {
    System.out.println("Using type based parking strategy..");
    // fake spot number
    return 101; // implement type based spot parking logic
  }
}

class PriorityParkingStrategy implements ParkingStrategy {

  @Override
  public int findAvailableSlot(Vehicle vehicle) {
    System.out.println("Using priority parking strategy..");
    // fake spot number
    return 1011; // implement priority spot parking logic
  }
}

As you can see, we can easily create new strategies by implementing the ParkingStategy interface and implement the logic based on new requirements without modifying the existing code.

  1. Define Context Class
class ParkingLot {
  // default strategy
  private ParkingStrategy strategy = new ClosestSpotParkingStrategy();

  public ParkingLot() {}

  // or provide default strategy
  public ParkingLot(ParkingStrategy strategy) {
    this.strategy = strategy;
  }

  public void setParkingStrategy(ParkingStrategy strategy) {
    this.strategy = strategy;
  }

  public int parkVehicle(Vehicle vehicle) {
    int slot = strategy.findAvailableSlot(vehicle);
    System.out.println("Parking " + vehicle.name() + " in slot -> " + slot + ".");
    return slot;
  }
}
  1. Client code implementation
public class Application {

  public static void main(String[] args) {
    final var parkingLot = new ParkingLot();
    parkingLot.setParkingStrategy(new PriorityParkingStrategy());
    parkingLot.parkVehicle(new Car("Rolls-Royce"));
  }
}

### Conclusion

The Strategy Pattern is a powerful behavioral design pattern that promotes flexibility and maintainability in your code. By encapsulating algorithms into separate classes (strategies), it allows you to switch between them at runtime, eliminating the need for complex conditional statements.

While Strategy Pattern introduces more classes, the benefits of decoupling the algorithm from the context often outweigh this drawback, especially in complex applications where behavior needs to change dynamically.