Skip to content
aipatterns.dev

Refactoring techniqueMoving Features between Objects

Remove Middle Man

Let clients call the delegate directly when a class does little more than forward calls to it.

Problem

A class has grown a forwarding method for every method of its delegate. Each new feature on the delegate requires a matching pass-through, and the class adds nothing but a hop.

Solution

Expose the delegate and have clients call it directly. Delete the forwarding methods.

Before

TypeScript
class Warehouse {
  constructor(private stock: StockLedger) {}

  quantityOf(sku: string) { return this.stock.quantityOf(sku); }
  reserve(sku: string, qty: number) { return this.stock.reserve(sku, qty); }
  release(sku: string, qty: number) { return this.stock.release(sku, qty); }
  receive(sku: string, qty: number) { return this.stock.receive(sku, qty); }
  adjust(sku: string, delta: number, reason: string) {
    return this.stock.adjust(sku, delta, reason);
  }
}

// client
warehouse.reserve("SKU-1182", 3);

After

TypeScript
class Warehouse {
  constructor(readonly stock: StockLedger) {}
}

// client
warehouse.stock.reserve("SKU-1182", 3);

Why

  • Adding a ledger operation no longer needs a twin method on Warehouse.
  • Readers see which object actually does the work.
  • Less code to test and keep in sync.

Steps

  1. Add an accessor (or a readonly field) that exposes the delegate.
  2. For one forwarding method, replace each client call with a direct call on the delegate. Run the tests.
  3. Delete that forwarding method. Run the tests.
  4. Repeat for the remaining forwarders.
  5. If the server is now empty of behavior, consider Inline Class.

Watch out

  • Keep forwarders that add something: access control, logging, validation, transactions, or translation between models. Those are a Proxy or Facade, not a middle man.
  • Keep the hop if the delegate is an implementation detail you may swap, or if exposing it would let clients bypass invariants the server enforces.
  • Removing forwarders from a published API is a breaking change. Deprecate first.
  • The inverse is Hide Delegate. Balance the two: hide what is likely to change, expose what is stable.

Agent checklist

  • The forwarders add no logic, checks or translation.
  • Exposing the delegate does not let clients break the server's invariants.
  • All clients of each forwarder were redirected before deleting it.
  • Public API removals were deprecated or versioned.
  • Tests pass.

Treats these smells

See also

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