Skip to content
aipatterns.dev

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

TypeScript
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

TypeScript
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 accrueInterest on a checking account becomes a compile error instead of a runtime exception.
  • Subclasses no longer need defensive overrides.

Steps

  1. Find all callers of the method. Note which concrete types they call it on.
  2. Copy the method into each subclass that needs it. Run the tests.
  3. Delete the method from the superclass. The compiler shows every caller that used it through the superclass type.
  4. 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.
  5. 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.

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

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.