Refactoring techniqueDealing with Generalization
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.
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
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
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.
hourlyRateCentsis no longer nullable, because it exists only where it is set.- New contractor-only behavior has an obvious home.
Steps
- Create the empty subclass. Run the tests.
- 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.
- Move fields used only by the special case into the subclass with Push Down Field. Run the tests.
- 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.
- 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 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 instead.
- In the example,
Contractorstill inheritsbaseSalaryCents. If many such leftovers appear, the hierarchy is wrong; a shared superclass of two siblings may fit better. - The inverse is Collapse Hierarchy.
Related
- Inverse: Collapse Hierarchy.
- Fixes Large Class, Temporary Field and flag-based Switch Statements.
- Alternatives: Extract Class for composition, Replace Type Code with Subclasses 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.
Treats these smells
- Large ClassA class with too many fields, methods or responsibilities, so every change risks breaking something unrelated inside it.
- Temporary FieldA field that is set and meaningful only during one operation or in one special case, and is empty or stale the rest of the time.
- Switch StatementsThe same switch or if-chain over a type code is repeated across the codebase, so adding one case means editing every copy.
See also
- Extract SuperclassCreate a shared superclass for two classes with overlapping fields and methods, and pull the common parts up into it.
- Extract ClassSplit a class that does two jobs by moving one job's fields and methods into a new class it holds a reference to.
- Push Down MethodMove a superclass method into the only subclasses that use it, so the superclass describes only what all subclasses share.
- Push Down FieldMove a superclass field into the subclasses that use it, so other subclasses stop carrying state they never touch.
- 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.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.