Skip to content
aipatterns.dev

Refactoring techniqueMoving Features between Objects

Hide Delegate

Add a method on the server object that forwards to its delegate, so clients stop navigating through it to reach the delegate.

Problem

Clients call a.getB().getC().doThing(). They depend on the structure between a, b and c, so restructuring any link breaks every chain in the codebase.

Solution

Give the first object a method that does the navigation internally, and have clients call that method instead. The client then knows one object, not the path behind it.

Before

TypeScript
// In several controllers and jobs:
const approverEmail = order.getAccount().getBillingContact().email;
const region = order.getAccount().getBillingContact().address.region;

class Order {
  constructor(private account: Account) {}
  getAccount(): Account { return this.account; }
}

After

TypeScript
const approverEmail = order.billingContactEmail();
const region = order.billingRegion();

class Order {
  constructor(private account: Account) {}

  billingContactEmail(): string {
    return this.account.getBillingContact().email;
  }

  billingRegion(): string {
    return this.account.getBillingContact().address.region;
  }
}

Why

  • If billing contacts move from Account to a separate BillingProfile, only Order changes.
  • Clients express intent (billingRegion()) instead of navigation.
  • Fewer types appear in client imports, so tests need fewer mocks.

Steps

  1. Find a chain clients repeat. Pick the object they start from as the server.
  2. Add a method on the server that performs the navigation and returns the needed value or performs the action.
  3. Replace one client chain with the new method. Run the tests.
  4. Replace the remaining chains.
  5. If no client still needs the delegate itself, remove or narrow the server's accessor (getAccount()).

Watch out

  • Don't hide every delegate. Each forwarding method enlarges the server. When most of the server's methods are forwarders, it has become a Middle Man.
  • Chains through plain data structures or fluent builders (query.where(...).orderBy(...)) are not this smell. Leave them.
  • Forwarding methods should stay thin. If logic accumulates in them, it may belong in the delegate.
  • The inverse is Remove Middle Man. Use it when forwarding has taken over.

Agent checklist

  • The chain appears in more than one client, or its structure is likely to change.
  • The new method is named for what the client wants, not the path it takes.
  • All occurrences of the chain were replaced.
  • The server did not turn into a class of mostly forwarders.
  • Tests pass.

Treats these smells

See also

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