Skip to content

Mediator pattern

Overview

The mediator pattern allows for object interaction via a "mediator" which reduces direct dependencies and simplifies communication.

The Mediator pattern is a behavioural design pattern that facilitates communication between objects (colleagues) through a mediator object. Instead of objects communicating directly with each other, they send messages to the mediator, which then relays these messages to the appropriate recipients. This pattern helps to reduce the dependencies between communicating objects, making the system easier to maintain and modify.

Here's a quick breakdown of how it works:

  1. Mediator Interface: Declares the method used by colleagues to communicate with the mediator.
  2. Concrete Mediator: Implements the Mediator interface and coordinates communication between colleague objects.
  3. Colleague Objects: Know about the mediator and interact with it whenever they need to communicate with other colleague objects.

Advantages

  1. Reduced Coupling: The Mediator pattern helps to decouple the components, making them independent of each other. This leads to better maintainability and scalability.
  2. Centralized Control: The mediator centralizes the communication logic, making it easier to manage and debug the interactions between various components.
  3. Flexibility: Changes to the communication logic are centralized in the mediator, so you can modify the communication behaviour without altering the colleague objects.
  4. Simplified Object Interaction: By handling communication through a mediator, the complexity of object interaction is reduced, leading to cleaner and more comprehensible code.

Disadvantages

  1. Single Point of Failure: The mediator can become a single point of failure. If the mediator becomes too complex, it can be difficult to manage and debug.
  2. Performance Overhead: The additional layer of communication through the mediator can introduce performance overhead, especially if the communication logic is complex or frequent.
  3. Increased Complexity: While it reduces complexity between objects, it can introduce complexity within the mediator itself, making it potentially harder to understand and maintain.
  4. Limited Reusability: The mediator pattern can sometimes limit reusability of colleague classes, as they are designed to work with a specific mediator.

Example using a CQRS implementation

The Mediator pattern is often used alongside the CQRS pattern.

Using the mediator pattern we can define distinct Command and Query objects, and have these called as appropriate. The interfaces we implement tend to look something like this:

public interface ICommand<TResult> : IRequest<TResult> {}
public interface IQuery<TResult> : IRequest<TResult> {}

which work with a handler:

IRequestHandler<TRequest, TResponse>

where TRequest is a type of either command or query, and TResponse is the return type.

The Mediator pattern is useful where a request relies on more than just running business logic. Cross cutting concerns can also be implemented in the mediator pipeline to provide functionality such as logging, validation, security checks, etc. The pipeline wraps around the request handling (before and after is is called) and ensures all the steps will be executed in the correct order.

graph LR
    A[Caller] --> B(Request)
    B --> C(MediatR)
    C --> Pre-Processor --> D(Handler)
    D --> Post-Processor