Skip to content
aipatterns.dev

Refactoring techniqueDealing with Generalization

Push Down Field

Move a superclass field into the subclasses that use it, so other subclasses stop carrying state they never touch.

Problem

A field is declared in the superclass, but only one subclass reads or writes it. The other subclasses carry it around as null or a default, and readers cannot tell which subclasses it applies to.

Solution

Declare the field in the subclasses that use it and remove it from the superclass.

Before

TypeScript
abstract class Shipment {
  trackingNumber: string | null = null;
  pickupLocationId: string | null = null; // only used for click-and-collect
  constructor(readonly orderId: string) {}
}

class CourierShipment extends Shipment {
  dispatch(courier: Courier) {
    this.trackingNumber = courier.book(this.orderId);
  }
}

class StorePickup extends Shipment {
  assign(locationId: string) {
    this.pickupLocationId = locationId;
  }
}

After

TypeScript
abstract class Shipment {
  constructor(readonly orderId: string) {}
}

class CourierShipment extends Shipment {
  trackingNumber: string | null = null;
  dispatch(courier: Courier) {
    this.trackingNumber = courier.book(this.orderId);
  }
}

class StorePickup extends Shipment {
  pickupLocationId: string | null = null;
  assign(locationId: string) {
    this.pickupLocationId = locationId;
  }
}

Why

  • Each class declares only the state it owns, which documents its purpose.
  • Couriers no longer carry a pickup location that is always null.
  • Code that reads the field must now know the subclass, which makes the dependency visible.

Steps

  1. Find every read and write of the field. Confirm they all happen on a specific subclass.
  2. Declare the field in each subclass that uses it. Run the tests.
  3. Remove the field from the superclass. Fix compiler errors where callers reach it through the superclass type. Run the tests.
  4. Check serialization, database mappings and API responses that may emit the field for every subclass.

Watch out

  • Do not push down a field that generic code reads through the superclass, such as a reporting job that lists every shipment's tracking number. You would add type checks there.
  • Single-table ORM mappings may still require the column on the base entity. Moving the field in code may need a mapping change too.
  • Removing a field from an API response that always included it (even as null) can break clients.
  • The inverse technique is Pull Up Field.

Agent checklist

  • Every access to the field is made on a specific subclass.
  • No generic code reads it through the superclass type.
  • ORM mappings and serialized output were checked.
  • Each subclass that receives the field actually uses it.

Treats these smells

See also

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