Mediator
The Mediator pattern defines an object that encapsulates how a set of objects interact. This mediator promotes loose coupling by preventing objects from referring to each other explicitly and lets the mediation logic vary independently of the interacting objects. Instead of components communicating directly, they communicate through the mediator, which handles the interactions.
Usage
The Mediator pattern is particularly useful when you have a complex set of objects that interact in many different ways. It helps to centralize the control logic, making the system easier to understand, maintain, and extend. Common scenarios include:
- Chat applications: A chat room acts as a mediator between multiple users, handling message distribution.
- Air traffic control: The control tower mediates communication between airplanes to prevent collisions.
- Graphical User Interfaces (GUIs): A window manager can act as a mediator between different UI elements, handling events and updates.
- Complex workflows: When a system needs to orchestrate a series of dependent operations, a mediator can manage the flow of control.
Examples
-
Node.js Event Emitter: The Node.js
EventEmitterclass can be seen as a simple mediator. Components subscribe to events (throughon()) and the emitter handles dispatching those events to the appropriate listeners (throughemit()). Components don’t need to know about each other; they just interact with theEventEmitter.javascript const EventEmitter = require(’events’);
class Mediator extends EventEmitter { notify(event, data) { this.emit(event, data); } }
class ComponentA { constructor(mediator) { this.mediator = mediator; mediator.on(’eventB’, (data) => { console.log(‘Component A received eventB:’, data); }); }
doSomething() { this.mediator.notify(’eventA’, ‘Data from A’); } }
class ComponentB { constructor(mediator) { this.mediator = mediator; mediator.on(’eventA’, (data) => { console.log(‘Component B received eventA:’, data); }); }
doSomethingElse() { this.mediator.notify(’eventB’, ‘Data from B’); } }
const mediator = new Mediator(); const componentA = new ComponentA(mediator); const componentB = new ComponentB(mediator);
componentA.doSomething(); componentB.doSomethingElse();
-
Android Message Queues (Handler): In Android development, the
Handlerclass and its associated message queues function as a mediator between different threads (especially the UI thread and background threads). A background thread can send messages to the UI thread via aHandler, without the background thread needing direct access to UI elements or knowing their implementation details.java // Simplified example - In a real Android app, Handlers are more complex public class Mediator { private final Handler uiHandler;
public Mediator(Handler uiHandler) { this.uiHandler = uiHandler; } public void sendMessage(String message) { uiHandler.post(() -> { // Update UI with the message System.out.println("UI Thread received: " + message); }); }}
public class BackgroundTask { private final Mediator mediator;
public BackgroundTask(Mediator mediator) { this.mediator = mediator; } public void doWork() { // Simulate some work try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mediator.sendMessage("Work completed!"); }}
Specimens
15 implementationsThe Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and lets you vary their interaction independently. In this example, a ChatRoom acts as the mediator between User objects. Users don’t directly send messages to each other; they send them to the chatroom, which then broadcasts them to the relevant participants. Dart’s use of interfaces and classes makes OOP-based mediator implementations clean. The ChatRoom holds a list of users and manages their communication, adhering to the pattern’s core principle of centralizing control. The use of named parameters enhances readability and maintainability.
abstract class ChatParticipant {
void receive(String message, ChatParticipant sender);
String getName();
}
class ChatRoom {
private final List<ChatParticipant> participants = [];
void register(ChatParticipant participant) {
participants.add(participant);
}
void sendMessage(String message, ChatParticipant sender) {
for (var participant in participants) {
if (participant != sender) {
participant.receive(message, sender);
}
}
}
}
class User implements ChatParticipant {
final String name;
User({required this.name});
@override
void receive(String message, ChatParticipant sender) {
print('$name received from ${sender.getName()}: $message');
}
@override
String getName() => name;
void sendMessage(String message, ChatRoom chatRoom) {
chatRoom.sendMessage(message, this);
}
}
void main() {
final chatRoom = ChatRoom();
final user1 = User(name: 'Alice');
final user2 = User(name: 'Bob');
final user3 = User(name: 'Charlie');
chatRoom.register(user1);
chatRoom.register(user2);
chatRoom.register(user3);
user1.sendMessage('Hello, everyone!', chatRoom);
user2.sendMessage('Hi Alice!', chatRoom);
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and it lets you vary their interactions independently. In this Scala example, ChatMediator mediates communication between User objects. Each user registers with the mediator and sends messages to the mediator, which then distributes them to the appropriate recipients. Scala’s case classes and traits are well-suited for defining the components. Using an abstract Mediator allows flexibility for different mediation strategies, and is a common functional approach to structuring interactions.
trait Mediator {
def sendMessage(message: String, user: User): Unit
}
case class User(name: String, mediator: Mediator) {
def send(message: String): Unit = {
mediator.sendMessage(message, this)
}
def receive(message: String): Unit = {
println(s"${name} received: ${message}")
}
}
class ChatMediator extends Mediator {
private var users: List[User] = List.empty
override def sendMessage(message: String, user: User): Unit = {
println(s"${user.name} sends: ${message}")
users.filter(_ != user).foreach(_.receive(message))
}
def addUser(user: User): Unit = {
users = user :: users
}
}
object MediatorExample extends App {
val mediator = new ChatMediator()
val alice = User("Alice", mediator)
val bob = User("Bob", mediator)
val charlie = User("Charlie", mediator)
mediator.addUser(alice)
mediator.addUser(bob)
mediator.addUser(charlie)
alice.send("Hello everyone!")
bob.send("Hi Alice!")
charlie.send("What's up?")
}
The Mediator pattern defines a central object that encapsulates how a set of objects interact. It promotes loose coupling by preventing objects from referring to each other explicitly, and instead allowing communication through the mediator. This reduces dependencies and improves maintainability.
The example below simulates a chat room. ChatRoom is the Mediator. Participant objects (users) don’t know about each other, only the ChatRoom. They register with the ChatRoom and send messages to the ChatRoom which then broadcasts them to all other participants. This aligns with PHP’s object-oriented principles and avoids tight coupling between chat participants. Use of interfaces allows for flexibility with different participant types or chat room implementations.
<?php
/**
* Interface for Participants in the chat room.
*/
interface Participant
{
public function receive(string $message);
public function send(string $message);
}
/**
* The Mediator interface.
*/
interface ChatMediator
{
public function sendMessage(string $message, Participant $sender);
public function addParticipant(Participant $participant);
}
/**
* Concrete Mediator: The Chat Room.
*/
class ChatRoom implements ChatMediator
{
private array $participants = [];
public function addParticipant(Participant $participant): void
{
$this->participants[] = $participant;
}
public function sendMessage(string $message, Participant $sender): void
{
foreach ($this->participants as $participant) {
if ($participant !== $sender) {
$participant->receive($message);
}
}
}
}
/**
* Concrete Participant: A User.
*/
class User implements Participant
{
private string $name;
private ChatMediator $chatRoom;
public function __construct(string $name, ChatMediator $chatRoom)
{
$this->name = $name;
$this->chatRoom = $chatRoom;
$this->chatRoom->addParticipant($this);
}
public function receive(string $message): void
{
echo $this->name . " received: " . $message . PHP_EOL;
}
public function send(string $message): void
{
$this->chatRoom->sendMessage($message, $this);
}
}
// Usage
$chatRoom = new ChatRoom();
$alice = new User("Alice", $chatRoom);
$bob = new User("Bob", $chatRoom);
$charlie = new User("Charlie", $chatRoom);
$alice->send("Hello everyone!");
$bob->send("Hi Alice!");
?>
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and lets the interactions be varied independently of the objects. Here, we mediate communication between Colleague objects representing different parts of a chatroom (e.g., User, System). The Chatroom class acts as the Mediator, handling message delivery. This implementation leverages Ruby’s duck typing and flexible messaging. The Mediator pattern is well-suited to Ruby’s emphasis on behavioral patterns which often benefit from this kind of reduced coupling.
# Define the Colleague interface
module Colleague
def receive_message(message)
raise NotImplementedError
end
end
# Concrete Colleague: User
class User
include Colleague
attr_reader :name
def initialize(name, mediator)
@name = name
@mediator = mediator
end
def send_message(message)
@mediator.send_message(@name, message)
end
def receive_message(message)
puts "#{@name} received: #{message}"
end
end
# Concrete Colleague: System
class System
include Colleague
def initialize(mediator)
@mediator = mediator
end
def send_system_message(message)
@mediator.send_message("System", message)
end
def receive_message(message)
puts "System received: #{message}"
end
end
# The Mediator interface
module Mediator
def send_message(sender, message)
raise NotImplementedError
end
end
# Concrete Mediator: Chatroom
class Chatroom
def initialize
@users = []
@system = System.new(self)
end
def register_user(user)
@users << user
end
def send_message(sender, message)
@users.each do |user|
if user.name != sender
user.receive_message("#{sender}: #{message}")
end
end
@system.receive_message("#{sender}: #{message}") # System also receives all messages
end
end
# Usage
chatroom = Chatroom.new
alice = User.new("Alice", chatroom)
bob = User.new("Bob", chatroom)
chatroom.register_user(alice)
chatroom.register_user(bob)
alice.send_message("Hello, Bob!")
bob.send_message("Hi, Alice! How are you?")
chatroom.send_message("System", "Welcome to the chatroom!")
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by having objects communicate through the mediator, rather than directly with each other. This makes it easier to change the objects’ interactions without modifying those objects themselves.
In this Swift example, ChatMediator acts as the mediator, managing communication between User instances. Users don’t know about each other directly; they only interact via the send method of the mediator. This implementation uses a protocol (ChatMediatorProtocol) for dependency injection and testability. Swift’s use of protocols and a centralized mediator aligns with its emphasis on clear interfaces and controlled dependencies. Structs are used for User as they appropriately represent value types.
// Define the Mediator Protocol
protocol ChatMediatorProtocol {
func sendMessage(message: String, user: User)
}
// Concrete Mediator
class ChatMediator: ChatMediatorProtocol {
private var users: [User] = []
func addUser(user: User) {
users.append(user)
}
func sendMessage(message: String, user: User) {
for otherUser in users {
if otherUser != user {
otherUser.receive(message: message)
}
}
}
}
// Colleague: User
struct User {
let name: String
private weak var mediator: ChatMediatorProtocol?
init(name: String, mediator: ChatMediatorProtocol) {
self.name = name
self.mediator = mediator
}
func send(message: String) {
mediator?.sendMessage(message: message, user: self)
}
func receive(message: String) {
print("\(name) received: \(message)")
}
}
// Usage
let mediator = ChatMediator()
let alice = User(name: "Alice", mediator: mediator)
let bob = User(name: "Bob", mediator: mediator)
let charlie = User(name: "Charlie", mediator: mediator)
mediator.addUser(user: alice)
mediator.addUser(user: bob)
mediator.addUser(user: charlie)
alice.send(message: "Hello, everyone!")
bob.send(message: "Hi Alice!")
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by preventing objects from referring to each other explicitly, and instead letting them communicate through the mediator. This reduces dependencies and increases reusability.
This Kotlin example models a chat room. ChatRoom is the Mediator, managing communication between Participant objects. Participants don’t know each other directly; they only know the ChatRoom. ChatRoom receives messages from Participants and broadcasts them to all others. The use of interfaces and function types aligns well with Kotlin’s functional and concise style, making the interactions clean and easy to understand. Data classes enhance readability for participant information.
// Mediator Interface
interface ChatMediator {
fun sendMessage(message: String, participant: Participant)
}
// Concrete Mediator
class ChatRoom : ChatMediator {
private val participants = mutableListOf<Participant>()
fun addParticipant(participant: Participant) {
participants.add(participant)
}
override fun sendMessage(message: String, participant: Participant) {
participants.forEach {
if (it != participant) {
it.receive(message)
}
}
}
}
// Colleague Interface
interface Participant {
fun send(message: String)
fun receive(message: String)
}
// Concrete Colleague
data class User(val name: String, private val mediator: ChatMediator) : Participant {
override fun send(message: String) {
println("$name: $message")
mediator.sendMessage(message, this)
}
override fun receive(message: String) {
println("$name received: $message")
}
}
// Example Usage
fun main() {
val chatRoom = ChatRoom()
val user1 = User("Alice", chatRoom)
val user2 = User("Bob", chatRoom)
val user3 = User("Charlie", chatRoom)
chatRoom.addParticipant(user1)
chatRoom.addParticipant(user2)
chatRoom.addParticipant(user3)
user1.send("Hello everyone!")
user2.send("Hi Alice, good to see you!")
user3.send("Hey guys!")
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by preventing objects from referring to each other explicitly, and instead, they communicate through the mediator. This centralizes control and simplifies object interactions.
The Rust code implements a ChatRoom as the Mediator. Person structs represent the participants, and they only know the ChatRoom. Instead of directly messaging each other, they send_message to the ChatRoom, which then relays the message to all other Person instances. This uses Rust’s ownership and borrowing rules naturally, avoiding data races when updating shared state within the ChatRoom. The use of Arc<Mutex<>> enables safe shared mutable access to the list of participants.
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
// Mediator Interface
trait Mediator {
fn send_message(&self, message: String, sender: String);
}
// Concrete Mediator
struct ChatRoom {
participants: Arc<Mutex<HashMap<String, Person>>>,
}
impl ChatRoom {
fn new() -> Self {
ChatRoom {
participants: Arc::new(Mutex::new(HashMap::new())),
}
}
}
impl Mediator for ChatRoom {
fn send_message(&self, message: String, sender: String) {
let participants = self.participants.lock().unwrap();
for (name, person) in participants.iter() {
if name != &sender {
person.receive_message(message.clone(), sender.clone());
}
}
}
}
// Colleague Interface
trait Colleague {
fn set_mediator(&mut self, mediator: &ChatRoom);
fn send_message(&self, message: String);
fn receive_message(&mut self, message: String, sender: String);
}
// Concrete Colleague
struct Person {
name: String,
mediator: Option<Arc<ChatRoom>>,
}
impl Person {
fn new(name: String) -> Self {
Person {
name,
mediator: None,
}
}
}
impl Colleague for Person {
fn set_mediator(&mut self, mediator: &ChatRoom) {
self.mediator = Some(Arc::new(mediator.clone()));
}
fn send_message(&self, message: String) {
if let Some(mediator) = &self.mediator {
mediator.send_message(message, self.name.clone());
}
}
fn receive_message(&mut self, message: String, sender: String) {
println!("{}: {} said: {}", self.name, sender, message);
}
}
fn main() {
let chat_room = ChatRoom::new();
let alice = Arc::new(Person::new("Alice".to_string()));
let bob = Arc::new(Person::new("Bob".to_string()));
let charlie = Arc::new(Person::new("Charlie".to_string()));
let mut participants = chat_room.participants.lock().unwrap();
participants.insert(alice.name.clone(), alice.clone());
participants.insert(bob.name.clone(), bob.clone());
participants.insert(charlie.name.clone(), charlie.clone());
drop(participants); // Release the lock
alice.send_message("Hello everyone!".to_string());
bob.send_message("Hi Alice!".to_string());
charlie.send_message("Hey guys!".to_string());
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and it simplifies objects’ interaction. Instead of objects communicating directly, they communicate through the Mediator.
This Go example simulates a chat room with colleagues. Colleague interface defines the basic behavior of participants. Concrete colleagues Alice and Bob communicate via ChatRoom (the Mediator). The ChatRoom knows about all colleagues and handles message delivery.
It follows idiomatic Go by using interfaces to define contracts and structures to hold state. The message handling is centralized within the ChatRoom, keeping individual colleagues simple. Error handling is minimal for clarity, but could be expanded for a production system. The use of maps to store colleagues is also standard Go practice.
// colleague.go
package main
type Colleague interface {
Send(message string)
Receive(message string)
}
// alice.go
package main
import "fmt"
type Alice struct {
room ChatRoom
}
func (a *Alice) Send(message string) {
a.room.sendMessage(a, message)
}
func (a *Alice) Receive(message string) {
fmt.Printf("Alice received: %s\n", message)
}
// bob.go
package main
import "fmt"
type Bob struct {
room ChatRoom
}
func (b *Bob) Send(message string) {
b.room.sendMessage(b, message)
}
func (b *Bob) Receive(message string) {
fmt.Printf("Bob received: %s\n", message)
}
// chatroom.go
package main
type ChatRoom struct {
colleagues map[Colleague]bool
}
func NewChatRoom() *ChatRoom {
return &ChatRoom{
colleagues: make(map[Colleague]bool),
}
}
func (c *ChatRoom) Register(col Colleague) {
c.colleagues[col] = true
}
func (c *ChatRoom) sendMessage(sender Colleague, message string) {
for col := range c.colleagues {
if col != sender {
col.Receive(message)
}
}
}
// main.go
package main
func main() {
room := NewChatRoom()
alice := &Alice{room: room}
bob := &Bob{room: room}
room.Register(alice)
room.Register(bob)
alice.Send("Hello, Bob!")
bob.Send("Hi, Alice! How are you?")
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and it’s useful when complex communication exists between objects.
The C implementation uses a Mediator struct holding function pointers for communication between Colleague structures. Each colleague registers with the mediator and forwards requests to the mediator instead of directly to other colleagues. The mediator then determines which colleagues should receive the request. This avoids tight coupling that would occur with direct references. Using function pointers is a common approach for achieving polymorphism and abstracting behavior in C, fitting well with the pattern’s intent. A simple chatroom example illustrates the concept with two colleagues communicating through the mediator.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Forward declaration of Mediator
typedef struct Mediator_t Mediator;
// Colleague Interface
typedef struct Colleague_t {
char *name;
Mediator *mediator;
void (*send)(struct Colleague_t *colleague, const char *message);
void (*receive)(struct Colleague_t *colleague, const char *message);
} Colleague;
// Mediator Interface
typedef struct Mediator_t {
Colleague *colleague1;
Colleague *colleague2;
void (*send_message)(Mediator *mediator, Colleague *originator, const char *message);
} Mediator;
// Concrete Colleague
typedef struct {
Colleague base;
} ChatroomColleague;
// Concrete Mediator
typedef struct {
Mediator base;
} ChatroomMediator;
void chat_send(Colleague *colleague, const char *message) {
ChatroomColleague *cc = (ChatroomColleague*)colleague;
cc->base.mediator->send_message(cc->base.mediator, colleague, message);
}
void chat_receive(Colleague *colleague, const char *message) {
ChatroomColleague *cc = (ChatroomColleague*)colleague;
printf("%s receives: %s\n", cc->base.name, message);
}
void chatroom_send_message(Mediator *mediator, Colleague *originator, const char *message) {
ChatroomMediator *cm = (ChatroomMediator*)mediator;
if (originator == cm->base.colleague1) {
cm->base.colleague2->receive(cm->base.colleague2, message);
} else if (originator == cm->base.colleague2) {
cm->base.colleague1->receive(cm->base.colleague1, message);
}
}
Colleague *create_colleague(const char *name, Mediator *mediator) {
ChatroomColleague *colleague = (ChatroomColleague *)malloc(sizeof(ChatroomColleague));
if(!colleague) return NULL;
colleague->base.name = strdup(name);
colleague->base.mediator = mediator;
colleague->base.send = chat_send;
colleague->base.receive = chat_receive;
return (Colleague*)colleague;
}
Mediator *create_mediator(Colleague *colleague1, Colleague *colleague2) {
ChatroomMediator *mediator = (ChatroomMediator *)malloc(sizeof(ChatroomMediator));
if(!mediator) return NULL;
mediator->base.colleague1 = colleague1;
mediator->base.colleague2 = colleague2;
mediator->base.send_message = chatroom_send_message;
return (Mediator*)mediator;
}
void destroy_colleague(Colleague *colleague) {
if (colleague) {
free(colleague->base.name);
free(colleague);
}
}
void destroy_mediator(Mediator *mediator) {
if (mediator) {
free(mediator);
}
}
int main() {
Colleague *alice = create_colleague("Alice", NULL);
Colleague *bob = create_colleague("Bob", NULL);
Mediator *mediator = create_mediator(alice, bob);
alice->base.mediator = mediator;
bob->base.mediator = mediator;
alice->base.send(alice, "Hello, Bob!");
bob->base.send(bob, "Hi, Alice! How are you?");
destroy_colleague(alice);
destroy_colleague(bob);
destroy_mediator(mediator);
return 0;
}
The Mediator pattern defines a one-to-many dependency between objects. Instead of objects interacting directly, communication flows through a mediator object. This reduces coupling and promotes a more loosely coupled system, making it easier to modify and maintain.
Our example simulates a chat room where users communicate. The ChatMediator interface defines the communication method, and ConcreteMediator implements it, managing the user list and broadcasting messages. User objects only know the mediator; they don’t know about each other directly. This adheres to C++ principles of encapsulation and separation of concerns, using interfaces for flexibility and minimizing direct dependencies between classes. The class structure and standard library usage (like std::vector) are common C++ idioms.
#include <iostream>
#include <vector>
#include <string>
// Mediator Interface
class ChatMediator {
public:
virtual void sendMessage(int userId, std::string message) = 0;
virtual void addUser(int userId) = 0;
};
// Concrete Mediator
class ConcreteMediator : public ChatMediator {
private:
std::vector<int> users;
public:
void addUser(int userId) override {
users.push_back(userId);
}
void sendMessage(int userId, std::string message) override {
for (int user : users) {
if (user != userId) {
std::cout << "User " << userId << " to User " << user << ": " << message << std::endl;
}
}
}
};
// Colleague Interface
class User {
public:
User(int id, ChatMediator* mediator) : userId(id), mediator(mediator) {
mediator->addUser(id);
}
void send(std::string message) {
mediator->sendMessage(userId, message);
}
private:
int userId;
ChatMediator* mediator;
};
int main() {
ConcreteMediator mediator;
User user1(1, &mediator);
User user2(2, &mediator);
User user3(3, &mediator);
user1.send("Hello, everyone!");
user2.send("Hi user1!");
user3.send("Greetings!");
return 0;
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interactions independently. This example represents a simple chat room where users communicate through a Chatroom mediator instead of directly messaging each other. C# lends itself well to the Mediator pattern via interfaces, allowing for flexible collaboration between components without tight dependencies. The code uses classes representing users and a central mediator to handle message passing, keeping user classes focused on their own concerns.
// Mediator Interface
public interface IChatMediator
{
void SendMessage(string message, User user);
void RegisterUser(User user);
}
// Concrete Mediator
public class Chatroom : IChatMediator
{
private List<User> users = new List<User>();
public void SendMessage(string message, User user)
{
foreach (var u in users)
{
if (u != user)
{
u.Receive(message);
}
}
}
public void RegisterUser(User user)
{
users.Add(user);
}
}
// Colleague (User)
public class User
{
private string name;
private IChatMediator mediator;
public User(string name, IChatMediator mediator)
{
this.name = name;
this.mediator = mediator;
mediator.RegisterUser(this);
}
public void Send(string message)
{
mediator.SendMessage(message, this);
}
public void Receive(string message)
{
Console.WriteLine($"{name} received: {message}");
}
}
// Example usage
public class Program
{
public static void Main(string[] args)
{
Chatroom chat = new Chatroom();
User alice = new User("Alice", chat);
User bob = new User("Bob", chat);
User charlie = new User("Charlie", chat);
alice.Send("Hello, everyone!");
bob.Send("Hi Alice, and Charlie!");
charlie.Send("Hey guys!");
}
}
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and instead, they communicate through the mediator. Our code represents a simple chatroom where users can send and receive messages. The Chatroom class is the mediator, managing communication between User objects. Users only know the mediator, not other users. This implementation uses TypeScript’s type system and class-based structure to maintain clear interfaces and relationships, fitting an object-oriented approach appropriate for the language and the pattern’s intent of managing object interactions.
// Mediator interface
interface ChatMediator {
sendMessage(message: string, user: User): void;
addUser(user: User): void;
}
// Concrete Mediator
class Chatroom implements ChatMediator {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
sendMessage(message: string, user: User): void {
for (const u of this.users) {
if (u !== user) {
u.receive(message);
}
}
}
}
// Colleague interface
interface ChatUser {
sendMessage(message: string): void;
receive(message: string): void;
}
// Concrete Colleague
class User implements ChatUser {
private mediator: ChatMediator;
private name: string;
constructor(mediator: ChatMediator, name: string) {
this.mediator = mediator;
this.name = name;
mediator.addUser(this);
}
sendMessage(message: string): void {
console.log(`${this.name}: ${message}`);
this.mediator.sendMessage(message, this);
}
receive(message: string): void {
console.log(`${this.name} received: ${message}`);
}
}
// Usage
const chatroom = new Chatroom();
const alice = new User(chatroom, "Alice");
const bob = new User(chatroom, "Bob");
const charlie = new User(chatroom, "Charlie");
alice.sendMessage("Hello everyone!");
bob.sendMessage("Hi Alice!");
charlie.sendMessage("Hey guys, what's up?");
The Mediator pattern defines an object that encapsulates how a set of objects interact. This promotes loose coupling by preventing objects from referring to each other explicitly, and lets the mediation logic vary independently of the interacting classes. My code demonstrates this with a chatroom where users communicate through a Chatroom mediator. Users (concrete colleagues) don’t directly send messages to each other; they send messages to the chatroom, which then broadcasts them to all other users. This adheres to JavaScript’s flexible object composition and event handling capabilities, using a simple object to manage communication instead of tightly coupled class dependencies.
// Mediator Interface
class Chatroom {
constructor() {
this.users = [];
}
addUser(user) {
this.users.push(user);
}
sendMessage(message, user) {
this.broadcastMessage(message, user);
}
broadcastMessage(message, originUser) {
this.users.forEach(user => {
if (user !== originUser) {
user.receive(message, originUser);
}
});
}
}
// Colleague Interface
class User {
constructor(name, chatroom) {
this.name = name;
this.chatroom = chatroom;
this.chatroom.addUser(this);
}
sendMessage(message) {
this.chatroom.sendMessage(message, this);
}
receive(message, originUser) {
console.log(`${this.name} received from ${originUser.name}: ${message}`);
}
}
// Example Usage
const chatroom = new Chatroom();
const alice = new User("Alice", chatroom);
const bob = new User("Bob", chatroom);
const charlie = new User("Charlie", chatroom);
alice.sendMessage("Hello, everyone!");
bob.sendMessage("Hi Alice, good to see you!");
charlie.sendMessage("Hey guys!");
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by preventing objects from referring to each other explicitly, instead letting a mediator handle their communication. This allows you to vary the interactions independently of the interacting objects.
Here, ChatMediator is the mediator, coordinating messages between User objects. Each user only knows the mediator, not other users. The mediator implements send_message, which directs messages to the appropriate recipients based on their group membership. The use of abstract base classes (ABC, abstractmethod) and a central mediator class is consistent with Python’s support for both OOP and design patterns, enhancing maintainability and flexibility.
from abc import ABC, abstractmethod
class User(ABC):
def __init__(self, name, mediator):
self.name = name
self.mediator = mediator
@abstractmethod
def send_message(self, message):
pass
def receive_message(self, message):
print(f"{self.name} received: {message}")
class ConcreteUser(User):
def send_message(self, message):
self.mediator.send_message(self.name, message)
class Mediator(ABC):
@abstractmethod
def send_message(self, sender, message):
pass
class ChatMediator(Mediator):
def __init__(self):
self.users = {}
def add_user(self, user):
self.users[user.name] = user
def send_message(self, sender, message):
for name, user in self.users.items():
if name != sender:
user.receive_message(f"{sender}: {message}")
if __name__ == '__main__':
mediator = ChatMediator()
user1 = ConcreteUser("Alice", mediator)
user2 = ConcreteUser("Bob", mediator)
user3 = ConcreteUser("Charlie", mediator)
mediator.add_user(user1)
mediator.add_user(user2)
mediator.add_user(user3)
user1.send_message("Hello everyone!")
user2.send_message("Hi Alice!")
user3.send_message("What's up?")
The Mediator pattern defines a centralized object that encapsulates the interaction between multiple objects. Instead of objects interacting directly, they communicate through the mediator. This promotes loose coupling, allowing changes in one object to have minimal impact on others.
The Java implementation uses an abstract Mediator interface defining a send method for communication. Concrete Mediator classes manage the interactions. Colleague classes (like ChatRoom in this example) know their mediator but not other colleagues. Communication happens by colleagues sending messages to the mediator, which then directs them appropriately. This approach leverages Java’s interfaces and class structure for clear separation of concerns and fits well with OOP principles, making the code maintainable and extensible.
// Mediator Pattern in Java
import java.util.ArrayList;
import java.util.List;
// Abstract Mediator
interface ChatMediator {
void sendMessage(String msg, User user);
void addUser(User user);
}
// Concrete Mediator
class ConcreteChatMediator implements ChatMediator {
private List<User> users;
public ConcreteChatMediator() {
this.users = new ArrayList<>();
}
@Override
public void sendMessage(String msg, User user) {
for (User u : users) {
if (u != user) {
u.receive(msg);
}
}
}
@Override
public void addUser(User user) {
users.add(user);
}
}
// Abstract Colleague
interface User {
void send(String msg);
void receive(String msg);
}
// Concrete Colleague
class ConcreteUser implements User {
private ChatMediator mediator;
private String name;
public ConcreteUser(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
@Override
public void send(String msg) {
System.out.println(name + ": Sending " + msg);
mediator.sendMessage(msg, this);
}
@Override
public void receive(String msg) {
System.out.println(name + ": Received " + msg);
}
}
// Example Usage
public class MediatorExample {
public static void main(String[] args) {
ChatMediator mediator = new ConcreteChatMediator();
User user1 = new ConcreteUser(mediator, "Alice");
User user2 = new ConcreteUser(mediator, "Bob");
User user3 = new ConcreteUser(mediator, "Charlie");
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hello, everyone!");
user2.send("Hi Alice!");
}
}