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
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
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
- Add a factory method on the parent for each subclass, and replace every
new Subclass()with the factory. Run the tests. - Replace any
instanceof Subclasschecks with a field or method that answers the same question. - Declare fields on the parent for each constant-returning method. Add a constructor that takes them.
- Change each subclass to pass its constants to the parent constructor. Make the parent methods return the fields. Run the tests.
- Remove the overrides from the subclasses, then change each factory to construct the parent directly. Run the tests.
- 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.
instanceofchecks, 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.
Related
- Fixes Lazy Class and Speculative Generality.
- Inverse: Replace Type Code with Subclasses.
- Collapse Hierarchy 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.
-
instanceofchecks and class-name-based registrations were replaced. - The parent is concrete and the subclasses are deleted.
- Tests pass.
Treats these smells
- Lazy ClassA class does too little to justify the file, name and indirection it costs; fold it into its caller or delete it.
- Speculative GeneralityAbstractions, hooks and parameters built for future needs that never arrived make code harder to read without serving any current use.
- Parallel Inheritance HierarchiesEvery new subclass in one hierarchy forces a matching subclass in another, so two trees must always grow in lockstep.
See also
- Replace Type Code with SubclassesTurn each value of an immutable type code that drives conditionals into a subclass, and move the branch logic into overrides.
- Inline ClassFold a class that does too little into the class that uses it, moving its fields and methods across and deleting it.
- Collapse HierarchyMerge a subclass into its superclass (or the reverse) when the two have become nearly identical and the split no longer earns its cost.
- Replace Magic Number with Symbolic ConstantGive a literal number or string with special meaning a named constant, and use the name everywhere the value appears.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.