Skip to content

Replace Type Code with Subclasses

Take a field that holds a type code — an enum or string like "engineer", "manager", "sales" — that drives conditional behaviour, and replace each branch with a subclass. The shared base declares the operation; each subclass supplies its own version. The switch vanishes, and adding a new variant means adding a class instead of editing every conditional.

The smell is a type code paired with switching logic: one field whose value is repeatedly inspected by switch or chained if statements that pick behaviour. The trouble compounds. The same switch over the same code tends to appear in several methods, so every new type means editing each of them, and the compiler offers no help when you forget one. Polymorphism turns those scattered branches into a single dispatch the language performs for you.

An Employee whose pay depends on a type string checked by a switch. After, each employee type is a subclass that knows its own pay rule.

// Before
class Employee {
constructor(public type: string, public base: number) {}
payAmount(): number {
switch (this.type) {
case 'engineer': return this.base;
case 'manager': return this.base * 1.2;
case 'sales': return this.base + 500;
default: throw new Error(`unknown type ${this.type}`);
}
}
}
// After
abstract class Employee {
constructor(public base: number) {}
abstract payAmount(): number;
}
class Engineer extends Employee {
payAmount(): number { return this.base; }
}
class Manager extends Employee {
payAmount(): number { return this.base * 1.2; }
}
class Salesperson extends Employee {
payAmount(): number { return this.base + 500; }
}
flowchart TD
  subgraph Before["Before — a type code"]
    A["Employee<br/>type: 'engineer' | 'manager' | 'sales'"]
    A --> B{"switch type"}
    B --> C["pay logic for engineer"]
    B --> D["pay logic for manager"]
    B --> E["pay logic for sales"]
  end
  subgraph After["After — a hierarchy"]
    F["Employee (abstract)<br/>payAmount()"]
    F --> G["Engineer"]
    F --> H["Manager"]
    F --> I["Salesperson"]
  end
  Before -.->|"Replace Type Code with Subclasses"| After
A type-code switch becomes a class hierarchy with one method per subclass
  1. If the type code is not yet encapsulated, hide it behind an accessor first, so callers do not depend on the raw field.
  2. Create a subclass for one value of the type code. Provide a factory (or constructor) that returns the right subclass for a given code, so creation stays in one place.
  3. Move the behaviour for that value — one branch of the switch — into an overriding method on the subclass.
  4. Run your tests.
  5. Repeat for each remaining type-code value, removing each branch from the original conditional as you go.
  6. When the last branch is gone, delete the now-empty switch and, if nothing else needs it, the type-code field itself. The base operation is now abstract, supplied entirely by subclasses.

Use subclasses when the type is fixed for the life of the object and when several methods branch on the same code. Polymorphism collapses those branches and lets the compiler ensure every subclass implements the operation, so a forgotten case becomes a build error rather than a runtime surprise.

But subclasses are not always the right tool. If an object’s type changes during its lifetime — an order moving from “pending” to “shipped” to “delivered” — you cannot swap its class, so reach for the State pattern: hold a separate state object the entity can replace as it transitions. And if you only need to vary one algorithm rather than a whole family of behaviour, Strategy — injecting a behaviour object — is lighter than a full hierarchy. Choose subclasses for stable identity, State for changing identity, Strategy for swappable single algorithms.

What does Replace Type Code with Subclasses remove?
Why does the compiler help after this refactoring?
An order moves from pending to shipped to delivered during its life. Which pattern fits best?
When is the Strategy pattern lighter than a full subclass hierarchy?