Skip to main content

Replace Conditional with Polymorphism

What This Concept Is

When the same conditional (typically a switch on a type code) appears in more than one function, replace the type code with a class hierarchy (or a small map of strategy objects), and move each branch into the matching subclass.

Fowler:

I remove the duplication of the common switch logic by creating classes for each case and using polymorphism to bring out the type-specific behavior.

The prerequisite is that the conditional is recurring across multiple functions and dispatches on the same discriminator. If it is a one-off conditional inside one function, this is the wrong move -- use Decompose Conditional instead.

Why It Matters Here

This is the move that most often creates the occasion for a pattern (Strategy, State, Template Method). It is also the move that is most often misapplied: learners see one switch and immediately build a three-class hierarchy, trading a 10-line conditional for 40 lines of ceremony.

The rule is recurrence. If you have two or three functions that all switch on the same type and each branch has non-trivial behavior, polymorphism pays. Otherwise, it does not.

Concrete Example

Before -- the same switch appears in plumage and in (say) airSpeedVelocity:

function plumage(bird) {
switch (bird.type) {
case 'EuropeanSwallow':
return 'average';
case 'AfricanSwallow':
return bird.numberOfCoconuts > 2 ? 'tired' : 'average';
case 'NorwegianBlueParrot':
return bird.voltage > 100 ? 'scorched' : 'beautiful';
default:
return 'unknown';
}
}
// plus airSpeedVelocity with the same shape, elsewhere.

After -- one hierarchy, two polymorphic methods:

class Bird {
get plumage() { return 'unknown'; }
}
class EuropeanSwallow extends Bird {
get plumage() { return 'average'; }
}
class AfricanSwallow extends Bird {
constructor(data) { super(); this.numberOfCoconuts = data.numberOfCoconuts; }
get plumage() { return this.numberOfCoconuts > 2 ? 'tired' : 'average'; }
}
class NorwegianBlueParrot extends Bird {
constructor(data) { super(); this.voltage = data.voltage; }
get plumage() { return this.voltage > 100 ? 'scorched' : 'beautiful'; }
}

function birdFor(data) {
switch (data.type) {
case 'EuropeanSwallow': return new EuropeanSwallow(data);
case 'AfricanSwallow': return new AfricanSwallow(data);
case 'NorwegianBlueParrot':return new NorwegianBlueParrot(data);
default: return new Bird();
}
}

The factory keeps one switch (unavoidable -- you have to decide which subclass to build). Every other switch disappears.

Common Confusion / Misconception

One-off conditional. A single if/else in one function is not a candidate. Apply Decompose Conditional and Consolidate Conditional Expression first.

Premature hierarchy. If you only have one subtype today, polymorphism is ceremony. Wait for the second and third real subtypes before creating the hierarchy. Rule of three applies here too.

State vs Type. If the discriminator changes over the object's lifetime ("order goes from pending to paid to shipped"), a State pattern or a state machine is better than rigid subclasses.

How To Use It

Mechanics (Fowler):

  1. If classes do not exist yet, create them plus a factory.
  2. Move the conditional function to the superclass (use Extract Function if needed).
  3. Pick a subclass; override the superclass method with that leg's body.
  4. Repeat for each leg.
  5. Leave a default case in the superclass, or mark abstract.

Check Yourself

  1. What is the recurrence rule and why does it matter?
  2. Why does the factory still contain a switch? Is that a failure of the refactor?
  3. When is State pattern a better fit than Replace Conditional with Polymorphism?

Mini Drill or Application

Find a switch in your code that dispatches on a type. Search the codebase for other switch statements on the same discriminator. If you find at least two, sketch the class hierarchy. If you find only one, do Decompose Conditional instead and note the decision.

Video and Lecture References

Article References

External Exercises

  • Find a switch statement in open-source code (search GitHub for switch (type) in a language you know) and sketch the polymorphic alternative. Decide whether it would actually pay.
  • Refactoring.guru: Strategy pattern exercises - the close cousin pattern

Depth Path

  • Read This Only If Stuck - Fowler chunks 068, 069 (Replace Conditional with Polymorphism parts 1-2), 081 (Replace Type Code with Subclasses)
  • Optional deep dive: chunk 070-071 (Introduce Special Case) for the null-object variant; S3M3 Strategy pattern for the compositional alternative

Source Backbone

Refactoring is the canonical book backbone for this module. Use these sources after attempting the refactor and tests yourself.