Course program← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF
7Методические материалы

Indirection

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:

Orders Warehouse Billing Delivery Orders Warehouse Billing Delivery Coordinator directly – six connections through a middleman – four

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.