A logbook for curious waters
← Software Engineering Field Guide

Middle Man

Before you begin: Objects, constructor arguments, and dictionaries.

A Middle Man mostly forwards calls to another object without adding a useful responsibility or protecting a meaningful boundary. A reader opens one method only to be sent to another method with the same job.

Forwarding is not inherently wasteful. The smell appears when the extra object no longer buys a simpler contract, isolation from change, or behavior of its own. Remove Middle Man is the refactoring that lets callers reach the useful object directly. 1

A manager that only repeats Get

Suppose a tiny in-memory tag store returns a display label for a key. A manager class has been added, but it performs no validation, caching, authorization, or translation. It just repeats the store’s operation.

Original version

public sealed class TagStore
{
    private readonly Dictionary<string, string> labels =
        new Dictionary<string, string> { ["sea"] = "Ocean" };

    public string Get(string key)
    {
        return labels.GetValueOrDefault(key, "Unknown");
    }
}

public sealed class TagManager
{
    private readonly TagStore store;

    public TagManager(TagStore store) { this.store = store; }

    public string Get(string key)
    {
        return store.Get(key);
    }
}
TagManager manager = new TagManager(new TagStore());
Console.WriteLine(manager.Get("sea")); // Ocean

The caller constructs both objects, and TagManager.Get contributes only another hop to TagStore.Get.

class TagStore:
    def __init__(self):
        self.labels = {"sea": "Ocean"}

    def get(self, key):
        return self.labels.get(key, "Unknown")


class TagManager:
    def __init__(self, store):
        self.store = store

    def get(self, key):
        return self.store.get(key)
manager = TagManager(TagStore())
print(manager.get("sea"))  # Ocean

The caller constructs both objects, and TagManager.get contributes only another hop to TagStore.get.

Refactored version: use the store directly

public sealed class TagStore
{
    private readonly Dictionary<string, string> labels =
        new Dictionary<string, string> { ["sea"] = "Ocean" };

    public string Get(string key)
    {
        return labels.GetValueOrDefault(key, "Unknown");
    }
}
TagStore store = new TagStore();
Console.WriteLine(store.Get("sea")); // Ocean
class TagStore:
    def __init__(self):
        self.labels = {"sea": "Ocean"}

    def get(self, key):
        return self.labels.get(key, "Unknown")
store = TagStore()
print(store.get("sea"))  # Ocean

The store’s behavior is unchanged. The caller now names the object that actually performs the lookup. A missing key still returns Unknown.

A facade may look similar and still be useful

A facade can deliberately present a small, stable API over a complicated subsystem. A wrapper can also enforce permissions, translate errors, or hide a vendor-specific type. Such a class can remain valuable even when many methods are short.

Before removing a forwarding layer, ask what dependencies it prevents callers from acquiring. If it is the public contract that lets you replace a library later, deleting it may trade a small inconvenience for widespread coupling.

This example assumes the manager has no independent users or compatibility obligations beyond the callers being updated. A published API cannot be removed solely because its implementation is currently simple.

The tension with Message Chains is useful: too little forwarding can expose internals; too much can create an object full of relay methods. Choose the amount of indirection that explains and protects a real responsibility.

Sources

  1. Martin Fowler — Remove Middle Man, the inverse of Hide Delegate. The tag-store example is original.