This lesson was translated automatically – it may contain errors.
Indirection keeps coupling between classes low by assigning the responsibility for their interaction to an extra object – a middleman.
The clearest example is the Controller of the MVC triad: the view never addresses the model directly.
The point is easiest to see by counting the connections. Four modules that talk to each other directly give six connections; with a middleman there are four, and every module knows exactly one neighbour:
The gap grows faster than it looks: with ten modules there are already forty-five direct connections against ten through a middleman. The price is an extra hop in the code and the risk that the middleman collects all the logic and turns into a god object.
// A direct link: every module knows every other one
public class OrderModule
{
private readonly WarehouseModule _warehouse = new();
private readonly BillingModule _billing = new();
}
// Through a middleman: the modules know only about it
public class OrderCoordinator
{
public void Place(Order order)
{
_warehouse.Reserve(order);
_billing.Charge(order);
}
}
// A direct link: every module knows every other one
class OrderModule {
private:
WarehouseModule warehouse_;
BillingModule billing_;
};
// Through a middleman: the modules know only about it
class OrderCoordinator {
public:
OrderCoordinator(IWarehouse& warehouse, IBilling& billing)
: warehouse_(warehouse), billing_(billing) {}
void Place(const Order& order) {
warehouse_.Reserve(order);
billing_.Charge(order);
}
private:
IWarehouse& warehouse_;
IBilling& billing_;
};
// A direct link: every module knows every other one
type OrderModule struct {
warehouse *WarehouseModule
billing *BillingModule
}
// Through a middleman: the modules know only about it
type OrderCoordinator struct {
warehouse Warehouse
billing Billing
}
func (c *OrderCoordinator) Place(order Order) error {
if err := c.warehouse.Reserve(order); err != nil {
return err
}
return c.billing.Charge(order)
}
# A direct link: every module knows every other one
class OrderModule:
def __init__(self) -> None:
self._warehouse = WarehouseModule()
self._billing = BillingModule()
# Through a middleman: the modules know only about it
class OrderCoordinator:
def __init__(self, warehouse: Warehouse, billing: Billing) -> None:
self._warehouse = warehouse
self._billing = billing
def place(self, order: Order) -> None:
self._warehouse.reserve(order)
self._billing.charge(order)
// A direct link: every module knows every other one
class OrderModule {
private readonly warehouse = new WarehouseModule();
private readonly billing = new BillingModule();
}
// Through a middleman: the modules know only about it
class OrderCoordinator {
constructor(
private readonly warehouse: Warehouse,
private readonly billing: Billing,
) {}
place(order: Order): void {
this.warehouse.reserve(order);
this.billing.charge(order);
}
}
// A direct link: every module knows every other one
public class OrderModule {
private final WarehouseModule warehouse = new WarehouseModule();
private final BillingModule billing = new BillingModule();
}
// Through a middleman: the modules know only about it
public class OrderCoordinator {
private final Warehouse warehouse;
private final Billing billing;
public OrderCoordinator(Warehouse warehouse, Billing billing) {
this.warehouse = warehouse;
this.billing = billing;
}
public void place(Order order) {
warehouse.reserve(order);
billing.charge(order);
}
}
// A direct link: every module knows every other one
class OrderModule {
private val warehouse = WarehouseModule()
private val billing = BillingModule()
}
// Through a middleman: the modules know only about it
class OrderCoordinator(
private val warehouse: Warehouse,
private val billing: Billing,
) {
fun place(order: Order) {
warehouse.reserve(order)
billing.charge(order)
}
}
The price of the principle
A middleman is an extra level of indirection. It lowers coupling but makes reading harder: to see what will happen you have to follow the chain. The same trade-off is taken apart in detail in the Mediator pattern.