Replace Conditional Logic with Strategy

Destination
Symptom

A method whose body is a switch or chain of `if` branches dispatching on a type code. Adding a new variant means finding every dispatcher in the codebase and editing each one; the conditional grows wider every time a variant lands.

Goal

Each variant lives in its own Strategy class implementing a shared interface. Dispatch happens through a single virtual call — the conditional vanishes from the call site.

Before the refactoring

class Loan {
capital() {
if (this.expiry === null && this.maturity !== null) {
// term loan
return this.commitment * this.duration() * this.riskFactor();
}
if (this.expiry !== null && this.maturity === null) {
if (this.unusedPercentage !== 1.0) {
// revolver
return this.commitment * this.unusedPercentage * this.duration() * this.riskFactor();
}
// advised line
return this.outstandingRiskAmount() * this.duration()
+ this.unusedRiskAmount() * this.duration() * this.unusedPercentage;
}
return 0;
}
}

After the refactoring

class Loan {
constructor(strategy) {
this.capitalStrategy = strategy;
}
capital() {
return this.capitalStrategy.capital(this);
}
}
class TermLoanStrategy {
capital(loan) {
return loan.commitment * loan.duration() * loan.riskFactor();
}
}
class RevolverStrategy {
capital(loan) {
return loan.commitment * loan.unusedPercentage * loan.duration() * loan.riskFactor();
}
}
class AdvisedLineStrategy {
capital(loan) {
return loan.outstandingRiskAmount() * loan.duration()
+ loan.unusedRiskAmount() * loan.duration() * loan.unusedPercentage;
}
}
Example source: Adapted from Joshua Kerievsky's loan-calculator example in Refactoring to Patterns (Addison-Wesley, 2004), see the chapter on Replace Conditional Logic with Strategy. The Java original is translated to JavaScript and the three loan kinds are preserved as TermLoan, Revolver, and AdvisedLine strategies — same shape, different language.
Pressure

Type-code variants drive Shotgun Surgery: one new variant touches every dispatcher in scope. Cross-branch invariants are easy to miss because no single place owns the per-variant logic.

Tradeoff

Strategy introduces a class hierarchy and a runtime indirection. For two stable variants that will never grow, a plain conditional is often clearer and cheaper than the ceremony.

Relief

Adding a new variant is a new class — the dispatching code never changes. Each variant's behavior is locally readable and locally testable; the Liskov contract is the only cross-variant thing to mind.

Trap

Premature Strategy where the variants are stable, few, and unlikely to grow — the class hierarchy adds ceremony that earns no flexibility. One-method strategies whose only purpose is to satisfy the pattern make the system harder to read, not easier.