# Replace Subclass with Fields

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

- kind: technique
- category: organizing-data
- aliases: Remove Subclass
- canonical: https://aipatterns.dev/refactoring/techniques/replace-subclass-with-fields
- inspired-by: https://refactoring.guru/replace-subclass-with-fields

## 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

```ts
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

```ts
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](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-state-strategy.md).
- `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](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md). Go back when variants start needing different behavior.

## Related

- Fixes [Lazy Class](https://aipatterns.dev/refactoring/smells/lazy-class.md) and [Speculative Generality](https://aipatterns.dev/refactoring/smells/speculative-generality.md).
- Inverse: [Replace Type Code with Subclasses](https://aipatterns.dev/refactoring/techniques/replace-type-code-with-subclasses.md).
- [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md) applies when a single subclass is redundant.

## 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.
