Mediator Pattern in Go: Simplifying Complex Interactions

Explore the Mediator design pattern in Go, which encapsulates object interactions to promote loose coupling and simplify communication.

2.3.5 Mediator

In software design, managing complex interactions between objects can become cumbersome and lead to tightly coupled systems. The Mediator pattern offers a solution by encapsulating how a set of objects interact, promoting loose coupling and simplifying communication. This article delves into the Mediator pattern, its implementation in Go, and its practical applications.

Purpose of the Mediator Pattern

The Mediator pattern is designed to:

  • Encapsulate Interactions: Define an object that encapsulates how a set of objects interact.
  • Promote Loose Coupling: Prevent objects from referring to each other explicitly, reducing dependencies and enhancing modularity.

By centralizing communication, the Mediator pattern simplifies the maintenance and evolution of complex systems.

Implementation Steps

Implementing the Mediator pattern in Go involves several key steps:

1. Define Mediator Interface

The Mediator interface includes methods that facilitate communication between colleague objects. This interface acts as a contract for concrete mediators to implement.

1// Mediator interface defines the communication methods between colleagues.
2type Mediator interface {
3    Notify(sender Colleague, event string)
4}

2. Implement Concrete Mediator

The Concrete Mediator struct manages and coordinates the interactions between colleague objects. It implements the Mediator interface.

 1// ConcreteMediator implements the Mediator interface and coordinates communication.
 2type ConcreteMediator struct {
 3    airplanes map[string]Colleague
 4}
 5
 6func (m *ConcreteMediator) Notify(sender Colleague, event string) {
 7    // Handle communication logic based on the event and sender.
 8    for _, airplane := range m.airplanes {
 9        if airplane != sender {
10            airplane.Receive(event)
11        }
12    }
13}

3. Define Colleague Interfaces

Colleague interfaces represent the objects that interact through the mediator. These interfaces define methods for sending and receiving messages via the mediator.

1// Colleague interface represents an object that interacts through the mediator.
2type Colleague interface {
3    Send(event string)
4    Receive(event string)
5}

4. Implement Concrete Colleagues

Concrete Colleague structs implement the Colleague interface and use the mediator for communication.

 1// Airplane represents a concrete colleague that interacts through the mediator.
 2type Airplane struct {
 3    name     string
 4    mediator Mediator
 5}
 6
 7func (a *Airplane) Send(event string) {
 8    fmt.Printf("%s sends event: %s\n", a.name, event)
 9    a.mediator.Notify(a, event)
10}
11
12func (a *Airplane) Receive(event string) {
13    fmt.Printf("%s receives event: %s\n", a.name, event)
14}

When to Use

The Mediator pattern is particularly useful in scenarios where:

  • Complex Interactions: There are complex many-to-many interactions among related objects.
  • Simplified Communication: You need to simplify communication and reduce dependencies between objects.

Go-Specific Tips

  • Use Channels: In concurrent contexts, leverage Go’s channels for communication to enhance performance and safety.
  • Avoid Complexity: Be cautious to prevent the mediator from becoming overly complex, as it can become a bottleneck if it handles too much logic.

Example: Air Traffic Control System

Let’s explore a practical example of an air traffic control system using the Mediator pattern. In this system, airplanes (colleagues) communicate with each other through a control tower (mediator) to coordinate takeoffs and landings.

 1package main
 2
 3import "fmt"
 4
 5// Mediator interface defines the communication methods between colleagues.
 6type Mediator interface {
 7    Notify(sender Colleague, event string)
 8}
 9
10// ConcreteMediator implements the Mediator interface and coordinates communication.
11type ConcreteMediator struct {
12    airplanes map[string]Colleague
13}
14
15func (m *ConcreteMediator) Notify(sender Colleague, event string) {
16    for _, airplane := range m.airplanes {
17        if airplane != sender {
18            airplane.Receive(event)
19        }
20    }
21}
22
23// Colleague interface represents an object that interacts through the mediator.
24type Colleague interface {
25    Send(event string)
26    Receive(event string)
27}
28
29// Airplane represents a concrete colleague that interacts through the mediator.
30type Airplane struct {
31    name     string
32    mediator Mediator
33}
34
35func (a *Airplane) Send(event string) {
36    fmt.Printf("%s sends event: %s\n", a.name, event)
37    a.mediator.Notify(a, event)
38}
39
40func (a *Airplane) Receive(event string) {
41    fmt.Printf("%s receives event: %s\n", a.name, event)
42}
43
44func main() {
45    mediator := &ConcreteMediator{airplanes: make(map[string]Colleague)}
46
47    airplane1 := &Airplane{name: "Airplane 1", mediator: mediator}
48    airplane2 := &Airplane{name: "Airplane 2", mediator: mediator}
49
50    mediator.airplanes["Airplane 1"] = airplane1
51    mediator.airplanes["Airplane 2"] = airplane2
52
53    airplane1.Send("Requesting takeoff")
54    airplane2.Send("Requesting landing")
55}

Advantages and Disadvantages

Advantages:

  • Reduced Complexity: Simplifies complex interactions by centralizing communication.
  • Loose Coupling: Promotes loose coupling between objects, enhancing modularity and maintainability.

Disadvantages:

  • Mediator Complexity: The mediator can become complex if it handles too much logic, potentially becoming a bottleneck.
  • Single Point of Failure: The mediator becomes a single point of failure, which can impact the system if not managed properly.

Best Practices

  • Keep Mediator Simple: Ensure the mediator remains simple and focused on coordination rather than business logic.
  • Use Interfaces: Leverage interfaces to define clear contracts for mediators and colleagues, enhancing flexibility and testability.

Comparisons

The Mediator pattern is often compared with the Observer pattern. While both patterns facilitate communication between objects, the Mediator pattern centralizes communication through a mediator, whereas the Observer pattern relies on direct notifications between subjects and observers.

Conclusion

The Mediator pattern is a powerful tool for managing complex interactions between objects in Go applications. By encapsulating communication, it promotes loose coupling and simplifies system architecture. However, care must be taken to prevent the mediator from becoming overly complex. By following best practices and leveraging Go’s concurrency features, developers can effectively implement the Mediator pattern to enhance the maintainability and scalability of their applications.

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026