TUTORIAL SERIES
Design Patterns in Python: Mediator
Centralized Communicator
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:
- Managing Communication: Use it when lots of objects talk to each other, but you want to keep their chats organized through a middleman.
- Reducing Dependencies: Employ it to keep things from getting too attached to each other, making changes easier.
- 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.
Terminology and Key Components
In the Mediator pattern, understanding key elements is crucial:
- Components are classes containing business logic, linked to a mediator for reusability without knowing its specific class.
- Mediator Interface declares communication methods, allowing components to interact without directly coupling.
- Concrete Mediators manage relationships between components, holding references to and coordinating their actions without their explicit awareness.
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.
- Redux (JavaScript): Uses a mediator in managing state changes and communication between different parts of the application in JavaScript.
- Event Bus in Vue.js: Utilizes a mediator pattern for handling events and communication between components in Vue.js applications.
- Django Signals: Utilizes a mediator pattern for signaling and communication between different parts of a Django web application.
- RabbitMQ Message Broker: Utilizes a mediator pattern for message routing between publishers and subscribers in RabbitMQ.
- Event-driven Microservices Architectures: Utilizes a mediator pattern for communication between microservices in event-driven architectures.
- Kafka Message Broker: Employs a mediator pattern for handling message streaming and processing in distributed systems using Apache Kafka.
- Saga Middleware: Employs a mediator pattern for handling side effects in Redux applications using middleware.
- Angular Framework: Employs a mediator for communication between components in Angular applications, facilitating interaction without tight coupling.
- Electron IPC (Inter-Process Communication): Utilizes a mediator pattern for communication between main and renderer processes in Electron applications.
- WordPress Hooks and Actions: Employs a mediator pattern for event handling and communication between different parts of a WordPress website.
Best Practices and Considerations
When implementing the Mediator pattern, it’s crucial to consider both its advantages and potential drawbacks:
Pros and Cons
- Single Responsibility Principle: Consolidating communications within a mediator simplifies comprehension and maintenance.
- Open/Closed Principle: Introducing new mediators without modifying existing components supports extensibility.
- Reduced Coupling: Decreases tight dependencies between different components, enhancing flexibility.
- God Object Concerns: Over time, a mediator might grow into a complex “God Object,” handling multiple responsibilities.
Best Practices
- Clear Abstractions: Establish clear abstractions for components to maintain a cohesive and understandable interface.
- Utilize Interfaces: Implement interfaces to ensure uniformity among components interacting with the mediator.
Considerations
- Complexity Management: Large-scale mediators might introduce complexity, necessitating effective hierarchical structuring.
- Runtime Efficiency: Recursive operations within the mediator could impact system performance, requiring optimization strategies.
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! 👩💻