All Posts

Exploring the Strategy Design Pattern: Simplifying Software Design

Abstract AlgorithmsAbstract Algorithms
··4 min read

TL;DR

TLDR: If your code is full of if (type == A) doThis() else if (type == B) doThat(), you need the Strategy Pattern. It allows you to define a family of algorithms, encapsulate each one, and make them interchangeable at runtime. What is the Strategy ...

Cover Image for Exploring the Strategy Design Pattern: Simplifying Software Design

TLDR: If your code is full of if (type == A) doThis() else if (type == B) doThat(), you need the Strategy Pattern. It allows you to define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.


What is the Strategy Pattern? (The "No-Jargon" Explanation)

Imagine a GPS Navigation App.

  • The Goal: Go from Home to the Airport.
  • The Strategies:
    1. Car: Fastest route, uses highways.
    2. Walking: Shortest distance, uses sidewalks/parks.
    3. Public Transport: Uses bus/train lines.

You don't buy a separate phone for each mode. You just tap a button to switch the "Strategy." The destination remains the same; the algorithm to get there changes.

In code, instead of writing one giant function with 3 if blocks, we create 3 separate classes (CarStrategy, WalkStrategy, BusStrategy) and plug them into the GPS.


1. The Problem: The "If-Else" Hell

Let's say we are building an E-commerce Payment System.

public class PaymentService {
    public void pay(String type, int amount) {
        if (type.equals("CREDIT_CARD")) {
            System.out.println("Validating Card...");
            System.out.println("Charging " + amount);
        } else if (type.equals("PAYPAL")) {
            System.out.println("Redirecting to PayPal...");
            System.out.println("Charging " + amount);
        } else if (type.equals("CRYPTO")) {
            System.out.println("Checking Wallet Balance...");
            System.out.println("Charging " + amount);
        }
    }
}

Why is this bad?

  1. Hard to Maintain: If you want to add "Apple Pay", you have to modify the core class (Open/Closed Principle violation).
  2. Hard to Test: You have to test all logic in one massive file.

2. The Solution: The Strategy Pattern

We break this into 3 parts:

  1. The Interface (Strategy): Defines the common action (pay).
  2. The Concrete Strategies: The actual implementations.
  3. The Context: The class that uses the strategy.

Step 1: The Interface

public interface PaymentStrategy {
    void pay(int amount);
}

Step 2: The Implementations

public class CreditCardStrategy implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via Credit Card.");
    }
}

public class PayPalStrategy implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " via PayPal.");
    }
}

Step 3: The Context (Usage)

public class ShoppingCart {
    private PaymentStrategy strategy;

    // We can swap the strategy at runtime!
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void checkout(int amount) {
        strategy.pay(amount);
    }
}

Deep Dive: How It Works at Runtime

Let's trace how the data flows when a user switches payment methods.

Toy Scenario: A User Checking Out

StepUser ActionCode ExecutionObject in Memory
1Selects "Credit Card"cart.setPaymentStrategy(new CreditCardStrategy())strategy points to a CreditCard object.
2Clicks "Pay $100"cart.checkout(100) -> strategy.pay(100)Executes CreditCardStrategy.pay().
3Changes mind to "PayPal"cart.setPaymentStrategy(new PayPalStrategy())strategy pointer updates to PayPal object.
4Clicks "Pay $100"cart.checkout(100) -> strategy.pay(100)Executes PayPalStrategy.pay().

The Math (Complexity Reduction):

  • Before: Complexity = $O(N)$ inside one method (where N is number of payment types).
  • After: Complexity = $O(1)$ inside the Context. The complexity is distributed across N small classes.

Real-World Application: Game Character Skills

  • Context: A Role-Playing Game (RPG).
  • Problem: A character can equip different weapons.
    • Sword: Attack = "Slash"
    • Bow: Attack = "Shoot Arrow"
    • Magic Staff: Attack = "Cast Fireball"
  • Implementation:
    • Character class has a WeaponStrategy field.
    • When the player opens the inventory and equips a Bow, we call character.setWeapon(new BowStrategy()).
    • When the player presses "X" to attack, the code simply runs weapon.attack(). The Character class doesn't care how the attack happens, just that it happens.

Summary & Key Takeaways

  • Encapsulation: Each algorithm lives in its own class.
  • Interchangeability: You can swap behavior at runtime (e.g., changing weapons, changing payment methods).
  • Open/Closed Principle: You can add a new strategy (e.g., "BitcoinStrategy") without changing the existing code.

Practice Quiz: Test Your Design Skills

  1. Scenario: You are writing a sorting library. You want the user to be able to choose between "QuickSort" (for speed) and "MergeSort" (for stability). Which pattern fits best?

    • A) Singleton Pattern
    • B) Strategy Pattern
    • C) Factory Pattern
  2. Scenario: You have a Duck class. Some ducks fly, some swim, some are rubber ducks. You want to handle the fly() behavior dynamically.

    • A) Create a FlyStrategy interface with FlyWithWings and NoFly implementations.
    • B) Put a giant if-else block in the Duck class.
    • C) Create a separate subclass for every single combination (RubberDuck, MallardDuck, RedheadDuck...).
  3. Scenario: What is the main disadvantage of the Strategy Pattern?

    • A) It makes the code harder to test.
    • B) It increases the number of classes in the project.
    • C) It violates the Open/Closed Principle.

(Answers: 1-B, 2-A, 3-B)

Abstract Algorithms

Written by

Abstract Algorithms

@abstractalgorithms