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! 🚀
Top comments (0)