BLOG POSTS
    MangoHost Blog / Mediator Design Pattern in Java – Example and Explanation
Mediator Design Pattern in Java – Example and Explanation

Mediator Design Pattern in Java – Example and Explanation

The Mediator Design Pattern solves the problem of tight coupling between objects by introducing a central coordinator that handles communication between components. Instead of objects directly referencing and calling each other, they communicate through a mediator, making the system more maintainable and flexible. This pattern is particularly useful in complex systems where multiple objects need to interact, and you’ll learn how to implement it in Java, understand common pitfalls, and see real-world applications in GUI frameworks, chat systems, and air traffic control systems.

How the Mediator Pattern Works

The Mediator pattern centralizes complex communications and control logic between related objects. Rather than having objects communicate directly, they communicate through the mediator, which acts as an intermediary. This reduces dependencies between communicating objects, thereby reducing coupling.

The pattern consists of four key components:

  • Mediator Interface – Defines the contract for communication between components
  • Concrete Mediator – Implements the mediator interface and coordinates communication
  • Colleague Interface – Defines the contract for components that participate in the mediated communication
  • Concrete Colleagues – Implement the colleague interface and communicate via the mediator

Here’s the basic structure in Java:

// Mediator interface
public interface ChatMediator {
    void sendMessage(String message, User user);
    void addUser(User user);
}

// Colleague interface
public abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    
    public abstract void send(String message);
    public abstract void receive(String message);
}

Step-by-Step Implementation Guide

Let’s build a complete chat room system to demonstrate the Mediator pattern. This example shows how multiple users can communicate through a central chat mediator without knowing about each other directly.

Step 1: Create the Mediator Interface

public interface ChatMediator {
    void sendMessage(String message, User user);
    void addUser(User user);
    void removeUser(User user);
    List<User> getUsers();
}

Step 2: Implement the Concrete Mediator

import java.util.ArrayList;
import java.util.List;

public class ChatMediatorImpl implements ChatMediator {
    private List<User> users;
    
    public ChatMediatorImpl() {
        this.users = new ArrayList<>();
    }
    
    @Override
    public void addUser(User user) {
        this.users.add(user);
        System.out.println(user.getName() + " joined the chat");
    }
    
    @Override
    public void removeUser(User user) {
        this.users.remove(user);
        System.out.println(user.getName() + " left the chat");
    }
    
    @Override
    public void sendMessage(String message, User user) {
        for (User u : this.users) {
            // Don't send message back to sender
            if (u != user) {
                u.receive(message);
            }
        }
    }
    
    @Override
    public List<User> getUsers() {
        return new ArrayList<>(users);
    }
}

Step 3: Create the Abstract Colleague Class

public abstract class User {
    protected ChatMediator mediator;
    protected String name;
    
    public User(ChatMediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }
    
    public abstract void send(String message);
    public abstract void receive(String message);
    
    public String getName() {
        return name;
    }
}

Step 4: Implement Concrete Colleagues

public class ChatUser extends User {
    
    public ChatUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    
    @Override
    public void send(String message) {
        System.out.println(this.name + " sends: " + message);
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message) {
        System.out.println(this.name + " received: " + message);
    }
}

// Premium user with additional features
public class PremiumUser extends User {
    
    public PremiumUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }
    
    @Override
    public void send(String message) {
        System.out.println("[PREMIUM] " + this.name + " sends: " + message);
        mediator.sendMessage(message, this);
    }
    
    @Override
    public void receive(String message) {
        System.out.println("[PREMIUM] " + this.name + " received: " + message);
    }
    
    public void sendPrivateMessage(String message, User targetUser) {
        System.out.println("[PRIVATE] " + this.name + " to " + targetUser.getName() + ": " + message);
        targetUser.receive("[PRIVATE from " + this.name + "]: " + message);
    }
}

Step 5: Test the Implementation

public class MediatorPatternDemo {
    public static void main(String[] args) {
        ChatMediator mediator = new ChatMediatorImpl();
        
        User user1 = new ChatUser(mediator, "Alice");
        User user2 = new ChatUser(mediator, "Bob");
        User user3 = new PremiumUser(mediator, "Charlie");
        
        mediator.addUser(user1);
        mediator.addUser(user2);
        mediator.addUser(user3);
        
        user1.send("Hello everyone!");
        user2.send("Hi Alice!");
        user3.send("Hey guys!");
        
        // Premium user sends private message
        if (user3 instanceof PremiumUser) {
            ((PremiumUser) user3).sendPrivateMessage("Secret message", user1);
        }
    }
}

Real-World Examples and Use Cases

The Mediator pattern appears in many real-world scenarios where you need to manage complex interactions between multiple components:

GUI Components Example

public class DialogMediator {
    private Button submitButton;
    private CheckBox termsCheckbox;
    private TextField emailField;
    
    public void componentChanged(Component component) {
        if (component == termsCheckbox) {
            submitButton.setEnabled(termsCheckbox.isChecked() && 
                                   !emailField.getText().isEmpty());
        } else if (component == emailField) {
            submitButton.setEnabled(termsCheckbox.isChecked() && 
                                   !emailField.getText().isEmpty());
        }
    }
}

Air Traffic Control System

public class AirTrafficControlTower implements AirTrafficMediator {
    private List<Aircraft> aircrafts = new ArrayList<>();
    
    @Override
    public void requestLanding(Aircraft aircraft) {
        // Check runway availability and coordinate with other aircraft
        for (Aircraft other : aircrafts) {
            if (other != aircraft && other.isLanding()) {
                aircraft.waitForLanding();
                return;
            }
        }
        aircraft.land();
    }
    
    @Override
    public void requestTakeoff(Aircraft aircraft) {
        // Similar coordination logic for takeoff
        aircraft.takeoff();
    }
}

Common real-world applications include:

  • Swing/JavaFX Applications – Managing interactions between UI components
  • Workflow Engines – Coordinating different steps in business processes
  • Game Development – Managing interactions between game objects
  • Microservices – API gateways acting as mediators between services
  • Event-Driven Systems – Message brokers like Apache Kafka or RabbitMQ

Comparison with Alternative Patterns

Pattern Purpose Communication Style Coupling Level Best Use Case
Mediator Centralize complex communications Many-to-one-to-many Low between colleagues Complex inter-object communication
Observer Notify multiple objects of state changes One-to-many Low between subject and observers Event notification systems
Command Encapsulate requests as objects Invoker to receiver Low between invoker and receiver Undo/redo functionality, queuing
Facade Simplify complex subsystem Client to subsystem Low between client and subsystem Simplifying complex APIs

Performance Comparison

Aspect Direct Communication Mediator Pattern Observer Pattern
Memory Overhead Low Medium (mediator object) Medium (observer list)
Message Passing Speed Fastest Medium (extra indirection) Slower (multiple notifications)
Maintainability Poor (tight coupling) Excellent Good
Scalability Poor Good Excellent

Best Practices and Common Pitfalls

Best Practices:

  • Keep mediators focused – Don’t let your mediator become a god object handling everything
  • Use interfaces – Always define mediator contracts through interfaces for better testability
  • Consider async communication – For performance-critical applications, implement asynchronous message handling
  • Implement proper error handling – Mediators should gracefully handle failures in colleague communication
  • Use dependency injection – Inject mediators into colleagues rather than hard-coding dependencies
// Good practice: Async mediator with error handling
public class AsyncChatMediator implements ChatMediator {
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    private List<User> users = Collections.synchronizedList(new ArrayList<>());
    
    @Override
    public void sendMessage(String message, User sender) {
        CompletableFuture.runAsync(() -> {
            users.parallelStream()
                 .filter(user -> user != sender)
                 .forEach(user -> {
                     try {
                         user.receive(message);
                     } catch (Exception e) {
                         System.err.println("Failed to deliver message to " + user.getName());
                     }
                 });
        }, executor);
    }
}

Common Pitfalls to Avoid:

  • Mediator becomes too complex – Split large mediators into smaller, specialized ones
  • Performance bottlenecks – All communication goes through mediator, so optimize carefully
  • Circular dependencies – Ensure colleagues don’t try to create or manage their mediators
  • Memory leaks – Properly remove colleagues from mediator when they’re no longer needed
  • Over-engineering – Don’t use mediator for simple two-object communications

Memory Management Example:

public class ImprovedChatMediator implements ChatMediator {
    private final WeakHashMap<User, Boolean> users = new WeakHashMap<>();
    
    @Override
    public void addUser(User user) {
        users.put(user, Boolean.TRUE);
    }
    
    @Override
    public void sendMessage(String message, User sender) {
        // WeakHashMap automatically removes garbage collected users
        users.keySet().stream()
             .filter(user -> user != sender)
             .forEach(user -> user.receive(message));
    }
}

Testing Considerations:

// Mock mediator for unit testing
public class MockChatMediator implements ChatMediator {
    private List<String> messageLog = new ArrayList<>();
    
    @Override
    public void sendMessage(String message, User user) {
        messageLog.add(user.getName() + ": " + message);
    }
    
    public List<String> getMessageLog() {
        return new ArrayList<>(messageLog);
    }
    
    public void clearLog() {
        messageLog.clear();
    }
}

The Mediator pattern shines in scenarios with complex inter-object communication but requires careful design to avoid creating monolithic mediator classes. Consider using it alongside other patterns like Observer for event notifications or Command for request queuing. For more detailed information about design patterns, check out the Oracle Java OOP tutorial and the Refactoring Guru design patterns guide.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked