Behavioral Patterns «Prev Next»

Lesson 10Mediator Pattern Applicability
ObjectiveDecide when to use the Mediator design pattern.

When to Use the Mediator Design Pattern

The Mediator pattern is useful when the primary design problem lies in a web of interactions rather than in one object's local behavior. It defines an object that encapsulates how a cohesive set of participating objects interact. These participants, called colleagues, report events or request coordination through the mediator instead of encoding many direct peer-to-peer relationships.

A good mediator makes a collaboration easier to see, test, and change. It reduces concrete dependencies and gives cross-object rules an explicit home, but it does not eliminate complexity. Colleagues depend on the collaboration contract, while the mediator owns workflow, routing, and sequencing. An unnecessary mediator merely adds indirection.

A Practical Decision Rule for the Mediator Pattern

Mediator is a strong candidate when the following three conditions are present together:

  1. Several peer objects participate in one cohesive workflow.
  2. Direct collaboration causes those objects to know too much about one another or scatters workflow rules across their classes.
  3. One bounded coordinator can express the interaction policy more clearly than the existing network.

The number of objects alone is not decisive. Ten independent objects may not need a mediator, while three may justify one if their state transitions are tightly coordinated or frequently changed. Direct communication remains appropriate for simple, stable relationships.

A design can contain several mediators, but each should coordinate a separate interaction domain. Competing mediators for the same collaboration obscure ownership and event flow. The pattern also does not prohibit every colleague reference. Its purpose is to remove unnecessary concrete dependencies and centralize meaningful collaboration policy.

Strong Signs That a Mediator Will Improve the Design

These symptoms justify Mediator only when the interactions form one cohesive responsibility. Unrelated business rules should not be collected into a mediator merely to reduce the visible number of references. If centralization would produce one class that knows every feature in the application, the proposed boundary is too broad.

How Mediator Changes Object Dependencies

Without Mediator, a colleague may call several concrete peers and encode their response order. With Mediator, it reports a meaningful event through a mediator-facing contract. The mediator validates the event and invokes the appropriate operations. Each colleague retains its local service.

The pattern therefore replaces many colleague-to-colleague dependencies with colleague-to-mediator dependencies. It does not erase the collaboration; it gives the collaboration an explicit home. A fully connected group of n objects can have as many as n(n - 1) / 2 pairwise relationships. Real systems are not always fully connected, but this growth illustrates why a direct communication graph becomes difficult to reason about as participants are added.

Figure: 3 Object Interaction: Mediator as a Communication Hub
Figure 3 - Object Interaction: Mediator as a Communication Hub.

The diagram shows each colleague communicating with the mediator rather than maintaining direct references to every other colleague. The mediator receives events, applies the collaboration policy, and invokes the appropriate colleague operations. It represents one bounded collaboration, not a recommendation to route every application message through a single global object.

Demonstrating Mediator with a Java Chat Room

A chat room provides a compact example. Participants send through the room rather than addressing every peer. The room owns membership and routing, while each participant sends and receives through the collaboration contract. The complete program compiles as MediatorPatternDemo.java.

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
public class MediatorPatternDemo {
    interface ChatMediator {
        void register(Participant participant);
        void send(String message, Participant sender);
    }
    interface Participant {
        String name();
        void receive(String message, Participant sender);
    }
    static final class ChatRoom implements ChatMediator {
        private final Set<Participant> participants = new LinkedHashSet<>();
        @Override
        public void register(Participant participant) {
            participants.add(Objects.requireNonNull(participant));
        }
        @Override
        public void send(String message, Participant sender) {
            Objects.requireNonNull(message);
            if (!participants.contains(sender)) {
                throw new IllegalArgumentException("Sender is not registered");
            }
            for (Participant participant : participants) {
                if (participant != sender) {
                    participant.receive(message, sender);
                }
            }
        }
    }
    static final class ChatUser implements Participant {
        private final ChatMediator mediator;
        private final String name;
        ChatUser(ChatMediator mediator, String name) {
            this.mediator = Objects.requireNonNull(mediator);
            this.name = Objects.requireNonNull(name);
            mediator.register(this);
        }
        @Override
        public String name() {
            return name;
        }
        void send(String message) {
            System.out.println(name + " sends: " + message);
            mediator.send(message, this);
        }
        @Override
        public void receive(String message, Participant sender) {
            System.out.println(name + " receives from " + sender.name() + ": " + message);
        }
    }
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom();
        ChatUser alice = new ChatUser(chatRoom, "Alice");
        ChatUser bob = new ChatUser(chatRoom, "Bob");
        new ChatUser(chatRoom, "Charlie");
        alice.send("Hi, everyone!");
        bob.send("Hello, Alice!");
    }
}

Expected Output

Alice sends: Hi, everyone!
Bob receives from Alice: Hi, everyone!
Charlie receives from Alice: Hi, everyone!
Bob sends: Hello, Alice!
Alice receives from Bob: Hello, Alice!
Charlie receives from Bob: Hello, Alice!

ChatMediator is the mediator contract. ChatRoom owns routing policy. Participant defines the required colleague operations. ChatUser depends on the mediator rather than every user. The main method creates and connects the participants.

Simple broadcast can resemble Observer, but this example demonstrates Mediator because participants send through a central object that owns membership and routing rules. The distinction becomes stronger when the mediator enforces permissions, private-room membership, message destinations, or other interaction policy. If the only requirement were to notify subscribers that something changed, Observer or an event mechanism would usually be the clearer choice.

This program is synchronous and in-process. A production chat platform also requires authentication, persistence, moderation, concurrency control, delivery guarantees, and network transport. Those responsibilities should not turn this small mediator into an application-wide service.

Benefits of a Well-Scoped Mediator

Inheritance is not required to vary a mediator. Composition, configuration, smaller policy objects, or separate contract implementations often keep variations clearer and independently testable.

Trade-Offs of Centralizing Collaboration

Divide an oversized mediator by cohesive workflow rather than by arbitrary class size. If the collaboration is governed by explicit phases and transitions, place those rules in a state machine used by the mediator. If requests require queuing, retry, logging, or undo, represent them with Command instead of making the mediator perform every responsibility.

Testing a Mediator-Based Collaboration

Testing should occur at two levels. First, test each colleague in isolation with a fake or stub mediator. Verify the events it reports and the local actions it performs when the mediator calls it. Second, test the concrete mediator as a collaboration unit with controlled colleagues. Verify routing, order, invalid events, duplicate registration, participant removal, error propagation, reentrant events, and boundary cases.

Stateful or safety-critical domains require stronger verification. A traffic-control mediator, for example, should model legal states and transitions explicitly. Tests must prove that invalid sequences are rejected, that ordering rules are preserved, and that partial failures cannot create contradictory signals. Centralizing a workflow makes its policy easier to test only when the policy is explicit.

Mediator Compared with Related Patterns

Two or three objects with simple, natural, and stable relationships usually communicate directly. Communication across process or service boundaries generally requires a broker, event bus, or orchestration service. A GoF Mediator may still organize in-process domain behavior, but it should not be confused with the infrastructure that transports messages.

Mediator Applicability Checklist

If most answers are yes, Mediator is a strong candidate. If the only yes answer is that one state change must be announced to several recipients, consider Observer first. If the proposed mediator would collect unrelated workflows, redesign the collaboration boundaries before applying the pattern.

Use Mediator when a cohesive group of objects is difficult to understand, reuse, or change because its interaction policy is distributed across a dense network of direct relationships. Keep the mediator bounded so that centralizing collaboration produces clarity rather than a new concentration of complexity. Direct communication is not inherently wrong. The pattern earns its place when the interaction policy itself has become a distinct responsibility.


SEMrush Software 10 SEMrush Banner 10