TUTORIAL SERIES

Design Patterns in Python: Mediator

Centralized Communicator

Amir Lavasani
7 min readJan 4, 2024

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.

Understanding the Mediator Pattern

What is the Mediator Design Pattern?

The Mediator Design Pattern is a behavioral design blueprint facilitating communication among objects by centralizing interactions through a mediator object.

It acts as a liaison, enabling objects to interact without direct dependencies, thereby promoting loose coupling and minimizing intricate inter-object connections.

When to Use the Mediator Pattern:

The Mediator Pattern is handy in software when:

  1. Managing Communication: Use it when lots of objects talk to each other, but you want to keep their chats organized through a middleman.
  2. Reducing Dependencies: Employ it to keep things from getting too attached to each other, making changes easier.
  3. Handling Complex Interactions: Use it for systems with many moving parts that need controlled interactions without becoming a tangled mess.

Practical Example: A Message Broker

Let’s create a simple Message Broker to demonstrate how the Mediator pattern works in practice. This broker will act as a middleman, allowing different parts of a system to send and receive messages without directly knowing about each other.

Using this Message Broker, components can talk to each other without worrying about the nitty-gritty details, making the system more flexible and easier to manage.

This example will showcase the essence of the Mediator pattern by decoupling message senders and receivers through an intermediary broker.

Dall-E generated image with the following concept: Central hub symbolizing the mediator, with radiating paths extending outward, representing different components interconnected through the mediator

Terminology and Key Components

In the Mediator pattern, understanding key elements is crucial:

  1. Components are classes containing business logic, linked to a mediator for reusability without knowing its specific class.
  2. Mediator Interface declares communication methods, allowing components to interact without directly coupling.
  3. Concrete Mediators manage relationships between components, holding references to and coordinating their actions without their explicit awareness.
Mediator design pattern structure diagram. Image from refactoring.guru

Mediator Pattern Implementation in Python

Step 1: Components

Define classes representing different components with a reference to the Mediator.

class Component:
"""
Represents a component with business logic.
"""
def __init__(self, mediator):
self._mediator = mediator

def send(self, message):
"""
Sends a message to the mediator.
"""
self._mediator.notify(message)

def receive(self, message):
"""
Receives and processes messages from the mediator.
"""
print(f"Component received message: {message}")

Step 2: Mediator Interface

Declare the Mediator interface for communication between components.

from abc import ABC, abstractmethod

class Mediator(ABC):
"""
Mediator interface declares communication methods.
"""
@abstractmethod
def notify(self, message):
"""
Notify method for sending messages to components.
"""
pass

Step 3: Concrete Mediator

Implement a concrete mediator that manages component interactions.

class ConcreteMediator(Mediator):
"""
Concrete Mediator manages communication between components.
"""
def __init__(self):
self._components = []

def add_component(self, component):
"""
Adds a component to the mediator.
"""
self._components.append(component)

def notify(self, message):
"""
Notifies all components with the message.
"""
for component in self._components:
component.receive(message)

Client Code

Demonstrate the usage of the Mediator pattern.

if __name__ == "__main__":
# Create mediator
mediator = ConcreteMediator()

# Create components and link them to the mediator
component1 = Component(mediator)
component2 = Component(mediator)

# Add components to the mediator
mediator.add_component(component1)
mediator.add_component(component2)

# Send messages through components
component1.send("Hello from Component 1")
component2.send("Hi from Component 2")

Message Broker using Mediator Pattern

This code illustrates a simple implementation of a Message Broker using the Mediator pattern in Python.

It showcases how message participants communicate through the Message Broker without direct connections, emphasizing the decoupling aspect of the Mediator pattern.

Step 1: Components (Message Participants)

Define classes representing different components that send and receive messages through the Mediator (Message Broker).

class Participant:
"""
Represents a message participant.
"""
def __init__(self, mediator, name):
self._mediator = mediator
self.name = name

def send_message(self, message):
"""
Sends a message through the mediator.
"""
self._mediator.send_message(message, self)

def receive_message(self, message):
"""
Receives and processes messages from the mediator.
"""
print(f"{self.name} received message: {message}")

Step 2: Mediator Interface (Message Broker)

Define the Mediator interface (Message Broker) for handling message passing between components.

from abc import ABC, abstractmethod

class MessageBroker(ABC):
"""
Mediator interface (Message Broker) declares message handling methods.
"""
@abstractmethod
def send_message(self, message, participant):
"""
Sends a message to a participant.
"""
pass

Step 3: Concrete Mediator (Concrete Message Broker)

Implement a concrete mediator that manages message passing between message participants.

class ConcreteMessageBroker(MessageBroker):
"""
Concrete Message Broker manages message passing between participants.
"""
def __init__(self):
self._participants = []

def add_participant(self, participant):
"""
Adds a participant to the broker.
"""
self._participants.append(participant)

def send_message(self, message, participant):
"""
Sends a message to all participants except the sender.
"""
for p in self._participants:
if p != participant:
p.receive_message(message)

Client Code

Demonstrate the usage of the Message Broker implementation.

if __name__ == "__main__":
# Create message broker
message_broker = ConcreteMessageBroker()

# Create participants and link them to the broker
participant1 = Participant(message_broker, "Participant 1")
participant2 = Participant(message_broker, "Participant 2")

# Add participants to the broker
message_broker.add_participant(participant1)
message_broker.add_participant(participant2)

# Send messages through participants
participant1.send_message("Hello from Participant 1")
participant2.send_message("Hi from Participant 2")

GitHub Repo 🎉

Explore all code examples and design pattern implementations on GitHub!

10 Real-World Use Cases for State Pattern

Here are real-world examples of the Mediator design pattern architecture observed in various well-known software systems.

  1. Redux (JavaScript): Uses a mediator in managing state changes and communication between different parts of the application in JavaScript.
  2. Event Bus in Vue.js: Utilizes a mediator pattern for handling events and communication between components in Vue.js applications.
  3. Django Signals: Utilizes a mediator pattern for signaling and communication between different parts of a Django web application.
  4. RabbitMQ Message Broker: Utilizes a mediator pattern for message routing between publishers and subscribers in RabbitMQ.
  5. Event-driven Microservices Architectures: Utilizes a mediator pattern for communication between microservices in event-driven architectures.
  6. Kafka Message Broker: Employs a mediator pattern for handling message streaming and processing in distributed systems using Apache Kafka.
  7. Saga Middleware: Employs a mediator pattern for handling side effects in Redux applications using middleware.
  8. Angular Framework: Employs a mediator for communication between components in Angular applications, facilitating interaction without tight coupling.
  9. Electron IPC (Inter-Process Communication): Utilizes a mediator pattern for communication between main and renderer processes in Electron applications.
  10. WordPress Hooks and Actions: Employs a mediator pattern for event handling and communication between different parts of a WordPress website.
Dall-E generated image with the following concept: AI ruling the future leading to a utopia or dystopia

Best Practices and Considerations

When implementing the Mediator pattern, it’s crucial to consider both its advantages and potential drawbacks:

Pros and Cons

  1. Single Responsibility Principle: Consolidating communications within a mediator simplifies comprehension and maintenance.
  2. Open/Closed Principle: Introducing new mediators without modifying existing components supports extensibility.
  3. Reduced Coupling: Decreases tight dependencies between different components, enhancing flexibility.
  4. God Object Concerns: Over time, a mediator might grow into a complex “God Object,” handling multiple responsibilities.

Best Practices

  1. Clear Abstractions: Establish clear abstractions for components to maintain a cohesive and understandable interface.
  2. Utilize Interfaces: Implement interfaces to ensure uniformity among components interacting with the mediator.

Considerations

  1. Complexity Management: Large-scale mediators might introduce complexity, necessitating effective hierarchical structuring.
  2. Runtime Efficiency: Recursive operations within the mediator could impact system performance, requiring optimization strategies.
Dall-E generated image with the following concept: Different social classes in the post-AI world.

Mediator Pattern’s Relations with Other Patterns

The Mediator pattern works closely with several other design patterns:

Mediator vs. Chain of Responsibility

Handles requests sequentially along a dynamic chain until one receiver manages it.

Contrarily, the Mediator centralizes communication among components without explicit chains.

Mediator vs. Command

Establishes unidirectional connections between senders and receivers to perform actions.

In contrast, the Mediator enables indirect communication between components without direct connections for mutual dependency elimination.

Mediator vs. Observer

Allows dynamic subscription and unsubscription of receivers from receiving requests.

The Mediator, on the other hand, fosters indirect communication among components through a mediator object, reducing mutual dependencies.

Mediator vs. Facade

Both patterns manage collaboration among classes, yet differ in their approach.

Facade simplifies interfaces to subsystems, hiding complexities for clients, while the Mediator focuses on centralizing communication between components without direct connections.

Conclusion

In conclusion, the Mediator pattern serves as a powerful tool for managing communication and reducing dependencies among components within a software system.

By centralizing interactions through a mediator object, the pattern promotes a loosely coupled architecture, easing maintenance and scalability. It eliminates direct connections between components, enhancing flexibility and reusability while simplifying the overall system design.

Hope you enjoyed the Facade pattern exploration Happy coding! 👩‍💻

Next on the Series 🚀

Read More 📜

The Series 🧭

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru Mediator
  3. Head First Design Patterns (Book)
  4. Mediator Design Pattern in Java
  5. sourcemaking Mediator Design Pattern
  6. Wikipedia Mediator Design Pattern

--

--

Amir Lavasani

I delve into machine learning 🤖 and software architecture 🏰 to enhance my expertise while sharing insights with Medium readers. 📃