# Extract Subclass

> Move features used only by some instances of a class into a new subclass, replacing the flag or nullable fields that marked them.

- kind: technique
- category: dealing-with-generalization
- canonical: https://aipatterns.dev/refactoring/techniques/extract-subclass
- inspired-by: https://refactoring.guru/extract-subclass

## Problem

A class has fields and methods that only apply to some of its instances. A flag or a nullable field marks those instances, and methods check that flag before doing the special work.

## Solution

**Create a subclass for the special instances, and move the fields and methods that only they use into it.** Construct the subclass wherever the flag used to be set.

## Before

```ts
class Employee {
  constructor(
    readonly name: string,
    readonly baseSalaryCents: number,
    readonly isContractor: boolean,
    readonly hourlyRateCents: number | null,
  ) {}

  monthlyCost(hoursWorked: number): number {
    if (this.isContractor) return (this.hourlyRateCents ?? 0) * hoursWorked;
    return this.baseSalaryCents / 12;
  }

  holidayAllowanceDays(): number {
    return this.isContractor ? 0 : 25;
  }
}
```

## After

```ts
class Employee {
  constructor(readonly name: string, readonly baseSalaryCents: number) {}

  monthlyCost(_hoursWorked: number): number {
    return this.baseSalaryCents / 12;
  }

  holidayAllowanceDays(): number {
    return 25;
  }
}

class Contractor extends Employee {
  constructor(name: string, readonly hourlyRateCents: number) {
    super(name, 0);
  }

  monthlyCost(hoursWorked: number): number {
    return this.hourlyRateCents * hoursWorked;
  }

  holidayAllowanceDays(): number {
    return 0;
  }
}
```

## Why

- The flag and its checks disappear; each class has straight-line logic.
- `hourlyRateCents` is no longer nullable, because it exists only where it is set.
- New contractor-only behavior has an obvious home.

## Steps

1. Create the empty subclass. Run the tests.
2. Add a constructor or factory that creates the subclass, and change the places that set the flag to use it. Keep the flag working for now. Run the tests.
3. Move fields used only by the special case into the subclass with [Push Down Field](https://aipatterns.dev/refactoring/techniques/push-down-field.md). Run the tests.
4. For each method that branches on the flag, override it in the subclass with the special branch, and remove that branch from the superclass. Run the tests after each method.
5. Delete the flag when no code reads it.

## Watch out

- Do not use a subclass if an instance can switch categories during its life (an employee becoming a contractor). Objects cannot change class. Use [Replace Type Code with State/Strategy](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md) or composition.
- Do not use it if the variation is along more than one axis (contractor and remote and part-time). Subclasses multiply. Extract a component with [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) instead.
- In the example, `Contractor` still inherits `baseSalaryCents`. If many such leftovers appear, the hierarchy is wrong; a shared superclass of two siblings may fit better.
- The inverse is [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).

## Related

- Inverse: [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).
- Fixes [Large Class](https://aipatterns.dev/refactoring/smells/large-class.md), [Temporary Field](https://aipatterns.dev/refactoring/smells/temporary-field.md) and flag-based [Switch Statements](https://aipatterns.dev/refactoring/smells/switch-statements.md).
- Alternatives: [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) for composition, [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md) when a type code drives the split.

## Agent checklist

- [ ] The special case is fixed at construction and never changes.
- [ ] Variation is along a single axis.
- [ ] Each flag branch moved to an override with tests green in between.
- [ ] The flag and its now-dead branches were deleted.
