Skip to content
aipatterns.dev

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

TypeScript
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

TypeScript
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

  1. 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.
  2. 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 abstract in the superclass.
  3. Copy the method into the superclass. The subclass copies now override it identically. Run the tests.
  4. Delete one subclass copy. Run the tests. Repeat for the others.
  5. 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.

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

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