# Replace Conditional with Polymorphism

> Replace a switch on an object's type or kind with one class per case that overrides a shared method, so new cases add code instead of editing it.

- kind: technique
- category: simplifying-conditional-expressions
- canonical: https://aipatterns.dev/refactoring/techniques/replace-conditional-with-polymorphism
- inspired-by: https://refactoring.guru/replace-conditional-with-polymorphism

## Problem

A conditional selects behavior by checking a type code, a `kind` field or `instanceof`. The same switch, with the same cases, appears in several functions. Each new kind forces you to find and edit every one of them.

## Solution

**Create a class (or an object implementing a shared interface) for each case, move each branch into an overriding method on its class, and replace the conditional with a single method call.**

## Before

```ts
type Notification = { channel: "email" | "sms" | "push"; to: string; body: string };

function send(n: Notification): Promise<void> {
  switch (n.channel) {
    case "email":
      return mailer.send({ to: n.to, subject: n.body.slice(0, 60), html: n.body });
    case "sms":
      return smsGateway.post(n.to, n.body.slice(0, 160));
    case "push":
      return pushService.notify(n.to, { alert: n.body });
  }
}

function costCents(n: Notification): number {
  switch (n.channel) {
    case "email": return 0;
    case "sms": return Math.ceil(n.body.length / 160) * 4;
    case "push": return 0;
  }
}
```

## After

```ts
interface Channel {
  send(to: string, body: string): Promise<void>;
  costCents(body: string): number;
}

const email: Channel = {
  send: (to, body) => mailer.send({ to, subject: body.slice(0, 60), html: body }),
  costCents: () => 0,
};

const sms: Channel = {
  send: (to, body) => smsGateway.post(to, body.slice(0, 160)),
  costCents: (body) => Math.ceil(body.length / 160) * 4,
};

const push: Channel = {
  send: (to, body) => pushService.notify(to, { alert: body }),
  costCents: () => 0,
};

const channels = { email, sms, push } satisfies Record<string, Channel>;
// Callers: channels[n.channel].send(n.to, n.body)
```

## Why

- Everything about SMS lives in one object. Adding WhatsApp is one new object and one map entry.
- Duplicated switches disappear, so no case can be forgotten in one of them.
- Each variant can be tested alone.

## Steps

1. Find all conditionals that switch on the same discriminator. If there is only one, and it is short, stop here.
2. Define the interface from the methods those conditionals implement.
3. Create one class or object per case. Start with all methods delegating back to the old function, so behavior is unchanged. Run the tests.
4. Move one branch body into its class method. Run the tests. Repeat branch by branch.
5. Add the lookup (map, factory or constructor parameter) that picks the right implementation once, at the edge.
6. Replace each switch with a call through the interface. Delete the old function when it has no callers. Run the tests.

## Watch out

- Do not apply this to a single switch that nothing else repeats. A switch with exhaustiveness checking (TypeScript `never`, Rust `match`, Java sealed types) is clear and safe.
- If operations change more often than kinds, polymorphism makes things worse: each new operation touches every class. Keep a discriminated union and pattern matching, or use [Visitor](https://aipatterns.dev/design-patterns/visitor.md).
- The switch moves, it does not vanish. Something still maps `"sms"` to the SMS object; keep that in one place.
- When the kind of an object changes at runtime, use [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md) rather than subclasses.
- No direct inverse technique exists. Collapsing polymorphic classes back into a switch is done manually with [Inline Class](https://aipatterns.dev/refactoring/techniques/inline-class.md) and [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).

## Related

- Fixes [Switch Statements](https://aipatterns.dev/refactoring/smells/switch-statements.md) and the [Shotgun Surgery](https://aipatterns.dev/refactoring/smells/shotgun-surgery.md) that repeated switches cause.
- Prepared by [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md) or [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md).
- Results in [Strategy](https://aipatterns.dev/design-patterns/strategy.md) or [State](https://aipatterns.dev/design-patterns/state.md).

## Agent checklist

- [ ] The same discriminator is switched on in more than one place, or the single switch is long and growing.
- [ ] Kinds change more often than operations.
- [ ] The kind-to-implementation mapping exists in exactly one place.
- [ ] Each branch moved separately with tests green between moves.
