DEV Community

Timevolt
Timevolt

Posted on

How the Strategy Pattern Made My Code Feel Like a Marvel Avengers Assemble

The Quest Begins (The "Why")

Honestly, I used to dread opening the payment module in our e‑commerce app. Every time we needed to add a new way to pay — Apple Pay, Google Pay, crypto — I’d find myself staring at a monster if‑else chain that spanned over a hundred lines. It felt like trying to defeat a final boss with a wooden sword: you could swing, but you kept missing the weak spot.

I remember one Friday afternoon, after a weekend launch, we got a bug report: the new “PayLater” option never triggered the discount logic. I dug into the code, traced the flow, and realized the condition for PayLater was buried three levels deep inside a nested switch that also handled tax calculations and shipping fees. Changing one line broke another, and the test suite started flashing red like a siren. I spent three hours debugging, and when I finally patched it, I felt like I’d just survived a raid — exhausted, but also convinced there had to be a better way.

That moment was my call to adventure. I wanted a system where adding a new payment method felt like equipping a new weapon, not rewriting the whole armory.

The Revelation (The Insight)

The treasure I uncovered was the Strategy Pattern. At its core, the pattern says: define a family of algorithms, encapsulate each one, and make them interchangeable. Instead of letting a single class decide which algorithm to run with conditionals, you give it a strategy object that knows how to do the job. The context class delegates the work, and you can swap strategies at runtime without touching the context.

Why does this matter? Because it obeys the Open/Closed Principle: your code is open for extension but closed for modification. You can drop in a new payment strategy without touching the existing payment processor, which means fewer bugs, easier tests, and a team that can work in parallel.

When I first saw it, I felt like I’d unlocked a new ability in a Zelda game — suddenly the map opened up, and I could see hidden paths everywhere.

Wielding the Power (Code & Examples)

Let’s look at the before code — our old payment processor:

// paymentProcessor.js (before)
class PaymentProcessor {
  processPayment(amount, method) {
    if (method === 'credit_card') {
      // validate card, talk to gateway, apply fees
      const fee = amount * 0.029;
      return amount + fee;
    }
    if (method === 'paypal') {
      // PayPal specifics
      const fee = amount * 0.034 + 0.30;
      return amount + fee;
    }
    if (method === 'apple_pay') {
      // Apple Pay specifics
      return amount; // no extra fee
    }
    // … many more branches …
    throw new Error(`Unsupported payment method: ${method}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Every time we added a new method, we edited this class. The method grew, the cognitive load skyrocketed, and unit tests required mocking the whole processor just to test one branch.

Now, the after version using Strategy:

// strategies/creditCardStrategy.js
const creditCardStrategy = {
  calculateFee: amount => amount * 0.029,
  // you could also add validate, authorize, etc.
};

// strategies/paypalStrategy.js
const paypalStrategy = {
  calculateFee: amount => amount * 0.034 + 0.30,
};

// strategies/applePayStrategy.js
const applePayStrategy = {
  calculateFee: amount => 0,
};

// paymentProcessor.js (after)
class PaymentProcessor {
  constructor(strategy) {
    this.strategy = strategy;
  }

  processPayment(amount) {
    const fee = this.strategy.calculateFee(amount);
    return amount + fee;
  }
}

// usage
const processor = new PaymentProcessor(creditCardStrategy);
const total = processor.processPayment(100); // 102.9

// swapping strategies at runtime
const paypalProcessor = new PaymentProcessor(paypalStrategy);
const paypalTotal = paypalProcessor.processPayment(100); // 103.70
Enter fullscreen mode Exit fullscreen mode

See the difference? The processor no longer knows how to calculate fees; it just asks its strategy. Adding a new method means creating a new strategy file and plugging it in — no changes to PaymentProcessor. The class stays thin, testable, and ready for extension.

Common traps to avoid

  1. Leaking conditionals back into the context – If you start checking if (this.strategy.type === 'credit_card') inside processPayment, you’ve defeated the purpose. Keep the context ignorant of the concrete strategy.
  2. Creating strategies that are too fine‑grained – A strategy should represent a whole algorithm, not a single line. If you find yourself making a strategy for each tiny step, consider whether a different pattern (like Template Method

Top comments (0)