Refactoring techniqueDealing with Generalization
Pull Up Method
Move a method that is identical across subclasses into their superclass so the logic lives and changes in one place.
Problem
Several subclasses contain the same method, copied or written independently. A fix in one copy does not reach the others, and the copies slowly diverge.
Solution
Move the method into the superclass and delete the subclass copies.
Before
abstract class Report {
constructor(protected rows: Row[], protected generatedAt: Date) {}
abstract render(): string;
}
class SalesReport extends Report {
render(): string { return this.header() + this.rows.map(formatSale).join("\n"); }
header(): string {
return `Generated ${this.generatedAt.toISOString()} | ${this.rows.length} rows\n`;
}
}
class InventoryReport extends Report {
render(): string { return this.header() + this.rows.map(formatStock).join("\n"); }
header(): string {
return `Generated ${this.generatedAt.toISOString()} | ${this.rows.length} rows\n`;
}
}After
abstract class Report {
constructor(protected rows: Row[], protected generatedAt: Date) {}
abstract render(): string;
protected header(): string {
return `Generated ${this.generatedAt.toISOString()} | ${this.rows.length} rows\n`;
}
}
class SalesReport extends Report {
render(): string { return this.header() + this.rows.map(formatSale).join("\n"); }
}
class InventoryReport extends Report {
render(): string { return this.header() + this.rows.map(formatStock).join("\n"); }
}Why
- One implementation to fix and test.
- New subclasses inherit the method instead of copying it again.
Steps
- Compare the copies line by line. If they differ only slightly, first make them identical with small refactorings in each subclass, running the tests in between.
- Check that every field and method the body uses exists in the superclass. If not, pull those up first with Pull Up Field, or declare them
abstractin the superclass. - Copy the method into the superclass. The subclass copies now override it identically. Run the tests.
- Delete one subclass copy. Run the tests. Repeat for the others.
- If the signatures differ, unify them before moving.
Watch out
- Do not pull up a method only some subclasses need. Others inherit behavior that makes no sense for them, which leads to Refused Bequest.
- If the copies are similar but differ in a step, pull up the skeleton and leave the steps in subclasses with Form Template Method.
- If the subclasses have no real "is-a" relationship, a shared helper function or a composed object is better than a deeper base class.
- The inverse technique is Push Down Method.
Related
- Inverse: Push Down Method.
- Fixes Duplicate Code.
- Preceded by Pull Up Field; generalized by Form Template Method.
Agent checklist
- All copies are behaviorally identical before the move.
- Every member the method uses is available in the superclass.
- Each subclass that inherits the method actually needs it.
- Subclass copies were deleted one at a time with tests green.
Treats these smells
See also
- Push Down MethodMove a superclass method into the only subclasses that use it, so the superclass describes only what all subclasses share.
- Pull Up FieldMove a field that every subclass declares in the same way into their shared superclass so it is defined once.
- Form Template MethodWhen subclass methods follow the same sequence of steps with different details, pull the sequence into the superclass and leave only the steps in subclasses.
- Extract SuperclassCreate a shared superclass for two classes with overlapping fields and methods, and pull the common parts up into it.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.