May the Strategy Be With You: A Dev’s Quest to Slay the If‑Else Dragon
The Quest Begins (The “Why”)
I still remember the first time I opened a legacy payment module and saw a monster-sized if/else chain stretching over a hundred lines. Each new payment method meant another branch, another copy-paste of validation logic, and a prayer that I hadn’t missed a case.
One Friday night, after deploying a tiny tweak that somehow broke Apple Pay, I spent three hours tracing through nested conditions, feeling like a stormtrooper trying to hit a moving target. The code was fragile, tests were a nightmare, and every new feature felt like adding another turret to a Death Star that kept missing its mark.
That’s when I realized I wasn’t just fighting bugs-I was fighting a design anti-pattern that made the codebase resist change. I needed a weapon that let me swap algorithms without rewriting the whole saga. Enter the Strategy Pattern.
The Revelation (The Insight)
The Strategy Pattern is simple: define a family of algorithms, encapsulate each one, and make them interchangeable. The context that uses them doesn’t know how the work gets done-it only knows what to ask for.
This gives us two superpowers:
- Open/Closed Principle – you can add new strategies without touching existing code.
- Testability – each strategy can be unit-tested in isolation, and the context can be tested with mocks.
When I first applied it, the relief was like hearing the rebel fleet jump into hyperspace after a long blockade. The if-else dragon shrank to a manageable lizard, and the code started to breathe.
Wielding the Power (Code & Examples)
The Struggle – Before
// payment.js – a classic if/else nightmare
function processPayment(amount, method, token) {
if (method === 'credit_card') {
// validate card, call gateway A
if (!validateCard(token)) throw new Error('Invalid card');
return gatewayA.charge(amount, token);
} else if (method === 'paypal') {
// validate PayPal token, call gateway B
if (!validatePayPal(token)) throw new Error('Invalid PayPal');
return gatewayB.charge(amount, token);
} else if (method === 'apple_pay') {
// validate Apple Pay token, call gateway C
if (!validateApplePay(token)) throw new Error('Invalid Apple Pay');
return gatewayC.charge(amount, token);
} else {
throw new Error('Unsupported payment method');
}
}
What’s wrong?
- Adding a new method means editing this function (risking regression).
- Validation logic is duplicated across branches.
- Unit testing requires mocking three different gateways in one test suite.
The Victory – After
First, we define a common interface for all payment strategies:
// paymentStrategy.js
class PaymentStrategy {
pay(amount, token) {
throw new Error('pay() must be implemented');
}
}
Then concrete strategies:
// creditCardStrategy.js
class CreditCardStrategy extends PaymentStrategy {
pay(amount, token) {
if (!validateCard(token)) throw new Error('Invalid card');
return gatewayA.charge(amount, token);
}
}
// paypalStrategy.js
class PayPalStrategy extends PaymentStrategy {
pay(amount, token) {
if (!validatePayPal(token)) throw new Error('Invalid PayPal');
return gatewayB.charge(amount, token);
}
}
// applePayStrategy.js
class ApplePayStrategy extends PaymentStrategy {
pay(amount, token) {
if (!validateApplePay(token)) throw new Error('Invalid Apple Pay');
return gatewayC.charge(amount, token);
}
}
Finally, the context that delegates to the chosen strategy:
// paymentProcessor.js
class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
process(amount, token) {
return this.strategy.pay(amount, token);
}
}
Usage looks like this:
const processor = new PaymentProcessor(new CreditCardStrategy());
processor.process(4999, cardToken); // $49.99 via card
// Switch to PayPal without touching the processor
processor.setStrategy(new PayPalStrategy());
processor.process(1999, paypalToken); // $19.99 via PayPal
What changed?
- Adding a new payment method now means creating a new class that implements
PaymentStrategy. No existing file is touched. - Each strategy is tiny, focused, and easy to test.
- The processor stays oblivious to the internals-it just calls
pay.
Common Traps to Avoid
| Trap | Why it’s Bad | How to Dodge It |
|---|---|---|
| Forgot to define a shared interface | Strategies end up with different method names, forcing the context to know concrete types. | Always start with an abstract base class or interface that declares the shared method(s). |
| Putting too much logic in the context | The context starts doing validation, logging, or formatting, defeating the purpose of encapsulation. | Keep the context thin; let each strategy handle its own concerns. |
| Instantiating strategies inside the context | You lock yourself to concrete classes again, making swapping harder. | Inject strategies via constructor or setter (dependency injection). |
Why This New Power Matters
Adopting the Strategy Pattern turned my payment module from a brittle monolith into a plug-and-play system. When the product team asked for cryptocurrency support last quarter, I added a CryptoStrategy in under an hour, ran the existing test suite (still green), and deployed without a feature flag or a hot-fix panic.
The mental shift is just as powerful: I now look for behavior that varies and ask, “Can I encapsulate this behind an interface?” That habit has seeped into other parts of my codebase-logging, notification delivery, even UI rendering-and the payoff is fewer regression bugs, clearer code reviews, and a team that actually enjoys touching old modules.
Ever felt like you’re stuck in a loop of copy-pasting the same validation logic? Give the Strategy Pattern a try. Start small-pick one messy conditional, extract the varying part into a strategy, and watch the tension melt away.
Your Turn
Pick a piece of code that makes you groan every time you touch it. Identify the algorithm that changes, wrap it in a strategy, and inject it. Share your before/after snippets in the comments-let’s see whose refactor feels like a lightsaber swing and whose feels like a gentle nudge from a wise old wizard.
May your code be clean, your tests be green, and your deployments be smooth! 🚀
Comments
No comments yet. Start the discussion.