Refactoring techniqueDealing with Generalization
Push Down Method
Move a superclass method into the only subclasses that use it, so the superclass describes only what all subclasses share.
Problem
A method sits in the superclass, but only one subclass (or a few) uses it. The others inherit it, and some override it to throw or do nothing. Readers of the superclass assume every subclass supports the operation.
Solution
Move the method into the subclasses that use it and delete it from the superclass.
Before
abstract class Account {
constructor(protected balanceCents: number) {}
abstract monthlyFee(): number;
accrueInterest(ratePerMonth: number): void {
this.balanceCents += Math.round(this.balanceCents * ratePerMonth);
}
}
class SavingsAccount extends Account {
monthlyFee() { return 0; }
}
class CheckingAccount extends Account {
monthlyFee() { return 500; }
accrueInterest(): void {
throw new Error("Checking accounts do not earn interest");
}
}After
abstract class Account {
constructor(protected balanceCents: number) {}
abstract monthlyFee(): number;
}
class SavingsAccount extends Account {
monthlyFee() { return 0; }
accrueInterest(ratePerMonth: number): void {
this.balanceCents += Math.round(this.balanceCents * ratePerMonth);
}
}
class CheckingAccount extends Account {
monthlyFee() { return 500; }
}Why
- The superclass now promises only what every account can do.
- Calling
accrueIntereston a checking account becomes a compile error instead of a runtime exception. - Subclasses no longer need defensive overrides.
Steps
- Find all callers of the method. Note which concrete types they call it on.
- Copy the method into each subclass that needs it. Run the tests.
- Delete the method from the superclass. The compiler shows every caller that used it through the superclass type.
- Fix those callers. Either change the variable's type to the subclass, or check the type first. If many callers need it generically, stop and reconsider. Run the tests.
- Delete the throwing or empty overrides in subclasses that never needed the method.
Watch out
- Do not push down a method that callers invoke polymorphically through the superclass type. You would replace a virtual call with type checks scattered across callers.
- If several subclasses would get identical copies, push it into an intermediate subclass instead, or keep it up.
- If the method will be needed by most new subclasses, leaving it up with a sensible default may be simpler.
- The inverse technique is Pull Up Method.
Related
- Inverse: Pull Up Method.
- Fixes Refused Bequest and Speculative Generality.
- Often paired with Push Down Field and Extract Subclass.
Agent checklist
- Only some subclasses use the method; others ignore, stub or throw.
- No caller relies on calling it through the superclass type, or those callers were updated.
- No duplicated copies were created across many subclasses.
- Throwing or empty overrides were removed.
Treats these smells
- Refused BequestA subclass inherits methods or data it does not want, overriding them to throw or do nothing, so the hierarchy lies about what it can do.
- Speculative GeneralityAbstractions, hooks and parameters built for future needs that never arrived make code harder to read without serving any current use.
See also
- Pull Up MethodMove a method that is identical across subclasses into their superclass so the logic lives and changes in one place.
- Push Down FieldMove a superclass field into the subclasses that use it, so other subclasses stop carrying state they never touch.
- Extract SubclassMove features used only by some instances of a class into a new subclass, replacing the flag or nullable fields that marked them.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.