# Middle Man

> A class mostly forwards calls to another object and adds nothing. Let clients talk to the real object or fold the wrapper away.

- kind: smell
- category: couplers
- canonical: https://aipatterns.dev/refactoring/smells/middle-man
- inspired-by: https://refactoring.guru/smells/middle-man

## Signs

- Most public methods of a class are one line that calls the same field: `return this.repo.find(id)`.
- Adding a method to the delegate forces a matching forwarding method on the wrapper.
- The wrapper has no state of its own beyond the delegate reference.
- Reviewers ask what the class is for, and the answer is "it wraps X".

## Why it happens

- [Hide Delegate](https://aipatterns.dev/refactoring/techniques/hide-delegate.md) was applied to cure [Message Chains](https://aipatterns.dev/refactoring/smells/message-chains.md), then kept being applied until the class became pure forwarding.
- Logic that once lived in the wrapper moved elsewhere, leaving the shell behind.
- An abstraction layer was added "in case we switch implementations", and the switch never came.

## Example

A `CustomerService` exists only to pass calls to the repository.

```ts title="before.ts"
interface Customer {
  id: string;
  email: string;
}

class CustomerRepository {
  private rows = new Map<string, Customer>();
  find(id: string): Customer | undefined {
    return this.rows.get(id);
  }
  save(c: Customer): void {
    this.rows.set(c.id, c);
  }
  delete(id: string): void {
    this.rows.delete(id);
  }
}

class CustomerService {
  constructor(private readonly repo: CustomerRepository) {}
  find(id: string) {
    return this.repo.find(id);
  }
  save(c: Customer) {
    this.repo.save(c);
  }
  delete(id: string) {
    this.repo.delete(id);
  }
}

// controller
const service = new CustomerService(new CustomerRepository());
service.save({ id: "c1", email: "a@example.com" });
```

Every method forwards. Callers gain nothing by going through the service.

```ts title="after.ts"
const customers = new CustomerRepository();
customers.save({ id: "c1", email: "a@example.com" });
```

If one method in the service had real logic, such as normalizing the email before saving, keep that method and remove the rest. Callers then use the repository for plain reads and writes.

## Treatment

- If most methods forward, use [Remove Middle Man](https://aipatterns.dev/refactoring/techniques/remove-middle-man.md) and let clients call the delegate.
- If only a few forwarding methods exist on an otherwise useful class, use [Inline Method](https://aipatterns.dev/refactoring/techniques/inline-method.md) on those forwarders at their call sites.
- If the wrapper adds a little behavior and forwards the entire delegate interface, consider [Replace Delegation with Inheritance](https://aipatterns.dev/refactoring/techniques/replace-delegation-with-inheritance.md), but only when the wrapper truly is a kind of the delegate.

## Payoff

- Fewer types and fewer hops when reading the code.
- New delegate methods are usable at once, without wrapper edits.
- The remaining classes each have a real reason to exist.

## When to ignore

- The wrapper is a deliberate seam: [Proxy](https://aipatterns.dev/design-patterns/proxy.md), [Decorator](https://aipatterns.dev/design-patterns/decorator.md), [Adapter](https://aipatterns.dev/design-patterns/adapter.md) or [Facade](https://aipatterns.dev/design-patterns/facade.md). These forward on purpose to add access control, caching, translation or simplification.
- The wrapper isolates a third-party library so the rest of the code does not import it. That boundary has value even when thin.
- The wrapper narrows a wide interface to the few methods clients may use.
- Removing it would reintroduce long [Message Chains](https://aipatterns.dev/refactoring/smells/message-chains.md) in client code.

## Agent checklist

- [ ] Count forwarding methods versus methods with real logic before removing the class.
- [ ] Confirm the wrapper is not a named pattern or a dependency boundary that the team relies on.
- [ ] Check dependency injection wiring and mocks in tests that reference the wrapper type.
- [ ] Update every caller to the delegate, then delete the wrapper in a separate step.
- [ ] Run the tests and confirm no layering or lint rule forbids the new direct dependency.
