Skip to content
aipatterns.dev

Refactoring techniqueOrganizing Data

Replace Subclass with Fields

Collapse subclasses that differ only in the constants their methods return into fields on the parent, then delete the subclasses.

Also known as
Remove Subclass

Problem

A hierarchy has several subclasses whose only job is to return different constant values: a label, a rate, a flag. There is no real behavior in them, yet each new variant needs a new class.

Solution

Add fields to the parent for the values, have the parent's methods return the fields, replace each subclass with a factory that sets those fields, and delete the subclasses.

Before

TypeScript
abstract class StoragePlan {
  abstract name(): string;
  abstract quotaGb(): number;
  abstract versioning(): boolean;
}

class StarterPlan extends StoragePlan {
  name() { return "Starter"; }
  quotaGb() { return 50; }
  versioning() { return false; }
}

class TeamPlan extends StoragePlan {
  name() { return "Team"; }
  quotaGb() { return 1_000; }
  versioning() { return true; }
}

class ArchivePlan extends StoragePlan {
  name() { return "Archive"; }
  quotaGb() { return 10_000; }
  versioning() { return true; }
}

After

TypeScript
class StoragePlan {
  private constructor(
    readonly name: string,
    readonly quotaGb: number,
    readonly versioning: boolean,
  ) {}

  static starter() { return new StoragePlan("Starter", 50, false); }
  static team() { return new StoragePlan("Team", 1_000, true); }
  static archive() { return new StoragePlan("Archive", 10_000, true); }
}

Why

  • Plans become data. A new tier is one line, and could come from configuration.
  • Four types collapse into one; readers see every plan's values side by side.
  • Values are comparable and serializable without type checks.

Steps

  1. Add a factory method on the parent for each subclass, and replace every new Subclass() with the factory. Run the tests.
  2. Replace any instanceof Subclass checks with a field or method that answers the same question.
  3. Declare fields on the parent for each constant-returning method. Add a constructor that takes them.
  4. Change each subclass to pass its constants to the parent constructor. Make the parent methods return the fields. Run the tests.
  5. Remove the overrides from the subclasses, then change each factory to construct the parent directly. Run the tests.
  6. Delete the subclasses and make the parent concrete.

Watch out

  • Only apply when subclasses differ in data. If any override contains real logic, keep that subclass or use Replace Type Code with State/Strategy.
  • instanceof checks, type guards, DI registrations and serializers keyed on class names will break. Search for them first.
  • Changing from methods (plan.quotaGb()) to fields (plan.quotaGb) changes call syntax. Keep getter methods if you want to avoid touching callers.
  • The inverse is Replace Type Code with Subclasses. Go back when variants start needing different behavior.

Agent checklist

  • Every subclass override returns a constant; none contains logic.
  • All construction goes through factories before subclasses are removed.
  • instanceof checks and class-name-based registrations were replaced.
  • The parent is concrete and the subclasses are deleted.
  • Tests pass.

Treats these smells

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.