Skip to content

Strategy Pattern: Dynamic Runtime Interchangeability

Swap an algorithm at runtime

Builds on Composition vs Inheritance and OCP - The Open/Closed Principle

Stage 3core2 min read

The refactored code implements the Strategy Design Pattern, a Behavioural pattern. It turns each behavior or algorithm into its own object. The main class, called the context, can then swap one for another.


1. How This Solves the Earlier Problems

The if-else branches are gone. Instead, the service holds a strategy object (composition, one object holding another) and hands the work to it (delegation). This fixes the problems from the Before code.

Problem in Old ApproachHow Strategy Pattern Solves It
Violation of Open/Closed Principle (OCP)To add a strategy (e.g., VipRiderStrategy), write a new class that implements the MatchingStrategy interface. The existing service code stays unchanged.
Code Becomes MessyThe nested if-else or switch logic is gone. Each branch now lives in its own strategy class, and the service calls it.
Difficult to Test or ReuseEach strategy is a small class on its own. You can test it alone, or reuse the proximity or queue logic in other modules (like a delivery service).
No Separation of ConcernsRideMatchingService only decides when to run the matching. The matching math lives in the strategy classes.

2. Suitable Scenarios for Strategy Pattern

Use the Strategy Pattern in these situations:

  • Multiple Interchangeable Algorithms: When there are different ways to do one operation (e.g., sorting algorithms, payment gateways, or compression formats) and you pick one depending on the situation.
  • Compliance with Open/Closed Principle (OCP): When new variants will keep arriving and you want to protect working code from regressions.
  • Elimination of Conditionals: When large if-else or switch blocks pick a behavior. Strategy replaces them with classes. The same call, match, runs different code depending on the object (polymorphism).
  • Behavior-Specific Unit Testing: When you want to test one calculation alone, without setting up the whole service.
  • Runtime Behavior Selection: When a class must change its behavior while running, based on things like the user's tier or system settings.

3. Structural Breakdown

ComponentResponsibility
Strategy Interface (MatchingStrategy)The one method signature (name and inputs) shared by all strategies.
Concrete Strategies (NearestDriverStrategy, etc.)Each one holds the steps of one algorithm.
Context (RideMatchingService)Holds a strategy object and passes the work to it.

4. Summary of the "Plug-and-Play" Architecture

Think of the Strategy Pattern as an Engine and Tires combination. The car (Context) stays the same, but you can swap the tires (Strategies) between street tires, winter snow tires and track racing tires, depending on the weather. The car does not change how it drives. It leaves the grip to whichever tires are fitted.

In this topic

After code1 file

After/Main.java122 linesGitHub
package DesignPatterns.BehaviouralPatterns.StrategyPattern.After;// ============================================================================// 1. STRATEGY INTERFACE// ============================================================================/** * Defines the unified contract for all ride-matching algorithms. * The Context class will interact only with this interface, making it * decoupled from specific algorithm implementations. */interface MatchingStrategy {    void match(String riderLocation);}// ============================================================================// 2. CONCRETE STRATEGIES// ============================================================================/** * Encapsulates standard distance-based proximity calculations. */class NearestDriverStrategy implements MatchingStrategy {    @Override    public void match(String riderLocation) {        System.out.println(            "Matching with the nearest available driver to " + riderLocation        );        // Isolated algorithmic logic goes here    }}/** * Encapsulates First-In, First-Out (FIFO) queue logic specialized for airport hubs. */class AirportQueueStrategy implements MatchingStrategy {    @Override    public void match(String riderLocation) {        System.out.println(            "Matching using FIFO airport queue for " + riderLocation        );        // Isolated algorithmic logic goes here    }}/** * Encapsulates peak-demand, surge pricing priority distribution algorithms. */class SurgePriorityStrategy implements MatchingStrategy {    @Override    public void match(String riderLocation) {        System.out.println(            "Matching rider using surge pricing priority near " + riderLocation        );        // Isolated algorithmic logic goes here    }}// ============================================================================// 3. CONTEXT CLASS// ============================================================================/** * Serves as the orchestration engine. It does not know how to compute * matching coordinates; it only knows *when* to execute the strategy. */class RideMatchingService {    // COMPOSITION OVER INHERITANCE: Maintains a reference to the strategy interface    private MatchingStrategy strategy;    /**     * Constructor Injection: Forces the client to provide a valid baseline behavior     * upon instantiation, avoiding null pointer issues.     */    public RideMatchingService(MatchingStrategy strategy) {        this.strategy = strategy;    }    /**     * Setter Injection: Provides RUNTIME FLEXIBILITY.     * Allows the application to dynamically shift algorithms on the fly     * without needing to reconstruct the service object.     */    public void setStrategy(MatchingStrategy strategy) {        this.strategy = strategy;    }    /**     * DELEGATION: Instead of running an if-else check, the context cleanly     * delegates the task directly to whichever strategy is currently active.     */    public void matchRider(String location) {        strategy.match(location);    }}// ============================================================================// 4. CLIENT CODE// ============================================================================public class Main {    public static void main(String[] args) {        // Scenario A: Initializing service with a localized Airport state        RideMatchingService rideMatchingService = new RideMatchingService(            new AirportQueueStrategy()        );        rideMatchingService.matchRider("Terminal 1");        System.out.println("------------------------------------------------");        // Scenario B: Initializing with a default strategy and changing behaviors dynamically        RideMatchingService rideMatchingService2 = new RideMatchingService(            new NearestDriverStrategy()        );        rideMatchingService2.matchRider("Downtown");        // Runtime modification: Switching behavioral execution instantly        rideMatchingService2.setStrategy(new SurgePriorityStrategy());        rideMatchingService2.matchRider("Downtown");    }}

Done with this topic?

How well do you know it?

PreviousStructural Design Patterns: Master Comparison