Behavioral Patterns  «Prev  Next»
Lesson 10 Mediator: applicability
Objective Decide when to Use the Mediator Pattern.

When to Use the Mediator Pattern and Applicability

Demonstrating the Mediator Pattern in Java

To provide a concrete demonstration of the Mediator pattern, consider the scenario of a chat room where multiple users can send messages to each other. Instead of users communicating directly with one another, they will use a centralized chat mediator. This mediator will control the delivery of messages, ensuring that every user gets the message intended for them.
  1. Mediator Interface: This is the contract that our concrete mediator will implement.
    public interface ChatMediator {
        void sendMessage(String msg, User user);
        void addUser(User user);
    }
    
  2. Concrete Mediator: This class implements the mediator interface and provides the logic to mediate between the users.
    import java.util.ArrayList;
    import java.util.List;
    
    public class ChatRoomMediator implements ChatMediator {
        private List<User> users;
    
        public ChatRoomMediator() {
            this.users = new ArrayList<>();
        }
    
        @Override
        public void sendMessage(String msg, User user) {
            for (User u : users) {
                // Do not send the message to the sender
                if (u != user) {
                    u.receive(msg);
                }
            }
        }
    
        @Override
        public void addUser(User user) {
            this.users.add(user);
        }
    }
    
  3. Abstract Colleague (User): All users will be extended from this 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);
    }
    
  4. Concrete User: This represents the users in the chat room.
    public class ChatUser extends User {
        public ChatUser(ChatMediator mediator, String name) {
            super(mediator, name);
            mediator.addUser(this);
        }
    
        @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 + " receives: " + message);
        }
    }
    
    

Demonstration of Mediator Pattern

public class MediatorPatternDemo {
    public static void main(String[] args) {
        ChatMediator mediator = new ChatRoomMediator();
        
        User alice = new ChatUser(mediator, "Alice");
        User bob = new ChatUser(mediator, "Bob");
        User charlie = new ChatUser(mediator, "Charlie");

        alice.send("Hi there!");
        bob.send("Hello, Alice!");
    }
}

In this demonstration, when Alice sends a message, the ChatRoomMediator ensures that all other users in the chat room, except for Alice, receive the message. The Mediator pattern effectively decouples the interactions between users, centralizing the messaging logic within the ChatRoomMediator. The Java classes above showcase the foundational architecture of the Mediator pattern, illustrating how complex interactions can be coordinated and streamlined using a centralized mediator.
The fundamental idea of the Mediator is that you insert one more objects in between all the interacting components. The components do not talk to each other directly. Instead, when they want to signal a change or get some data, they ask the Mediator for it. The intent of the Mediator pattern is to define an object that encapsulates how a set of objects interact. The Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. Though partitioning a system into many objects generally enhances reusability, proliferating interconnections tend to reduce it again. Lots of interconnections make it less likely that an object can work without the support of others. The system acts as though it were monolithic. Moreover, it can be difficult to change the system's behavior in any significant way, since behavior is distributed among many objects. As a result, you may be forced to define many subclasses to customize the system's behavior.
Object-oriented design encourages the distribution of behavioramong objects. Such distribution can result in an object structurewith many connections between objects; in the worst case, every objectends up knowing about every other.
The Mediator pattern is a good idea when:

  1. Changes in the state of one object affects many other objects.
  2. The sheer number of interconnections between objects makes the system unwieldy and hard to change.
  3. You want to be able to change the parts of a system independently from each other.

Figure: 3 Object Interaction: Mediator as a Communication Hub
Figure 3 - Object Interaction: Mediator as a Communication Hub.

In contrast to the diagram on the preceding page (see Figure2 on the previous page), the Mediator pattern can be used to design a controlled, coordinated communication model for a group of objects, eliminating the need for objects to refer to each other directly . The Mediator pattern suggests abstracting all object interaction details into a separate class, referred to as a Mediator, with knowledge about the interacting group of objects. Every object in the group is still responsible for offering the service it is designed for, but objects do not interact with each other directly for this purpose. The interaction between any two different objects is routed through the Mediator class and all objects send their messages to the mediator. The mediator then sends messages to the appropriate objects as per the requirements of the application. The resulting design has the following major advantages:
  1. With all the object interaction behavior moved into a separate (mediator) object, it becomes easier to alter the behavior of object interrelationships, by replacing the mediator with one of its subclasses with extended or altered functionality.
  2. Moving interobject dependencies out of individual objects results in enhanced object reusability.
  3. Because objects do not need to refer to each other directly, objects can be unit tested more easily.
  4. The resulting low degree of coupling allows individual classes to be modified without affecting other classes.