/ Design Patterns / Behavioral Patterns
Also known as: Intermediary,Controller
Intent
Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
Problem
Say you have a dialog for creating and editing customer profiles. It consists of various form controls such as text fields, checkboxes, buttons, etc.
Relations between elements of the user interface can become chaotic as the application evolves.
Some of the form elements may interact with others. For instance, selecting the “I have a dog” checkbox may reveal a hidden text field for entering the dog’s name. Another example is the submit button that has to validate values of all fields before saving the data.
Elements can have lots of relations with other elements. Hence, changes to some elements may affect the others.
By having this logic implemented directly inside the code of the form elements you make these elements’ classes much harder to reuse in other forms of the app. For example, you won’t be able to use that checkbox class inside another form, because it’s coupled to the dog’s text field. You can use either all the classes involved in rendering the profile form, or none at all.
Solution
The Mediator pattern suggests that you should cease all direct communication between the components which you want to make independent of each other. Instead, these components must collaborate indirectly, by calling a special mediator object that redirects the calls to appropriate components. As a result, the components depend only on a single mediator class instead of being coupled to dozens of their colleagues.
In our example with the profile editing form, the dialog class itself may act as the mediator. Most likely, the dialog class is already aware of all of its sub-elements, so you won’t even need to introduce new dependencies into this class.
UI elements should communicate indirectly, via the mediator object.
The most significant change happens to the actual form elements. Let’s consider the submit button. Previously, each time a user clicked the button, it had to validate the values of all individual form elements. Now its single job is to notify the dialog about the click. Upon receiving this notification, the dialog itself performs the validations or passes the task to the individual elements. Thus, instead of being tied to a dozen form elements, the button is only dependent on the dialog class.
You can go further and make the dependency even looser by extracting the common interface for all types of dialogs. The interface would declare the notification method which all form elements can use to notify the dialog about events happening to those elements. Thus, our submit button should now be able to work with any dialog that implements that interface.
This way, the Mediator pattern lets you encapsulate a complex web of relations between various objects inside a single mediator object. The fewer dependencies a class has, the easier it becomes to modify, extend or reuse that class.
Real-World Analogy
Aircraft pilots don’t talk to each other directly when deciding who gets to land their plane next. All communication goes through the control tower.
Pilots of aircraft that approach or depart the airport control area don’t communicate directly with each other. Instead, they speak to an air traffic controller, who sits in a tall tower somewhere near the airstrip. Without the air traffic controller, pilots would need to be aware of every plane in the vicinity of the airport, discussing landing priorities with a committee of dozens of other pilots. That would probably skyrocket the airplane crash statistics.
The tower doesn’t need to control the whole flight. It exists only to enforce constraints in the terminal area because the number of involved actors there might be overwhelming to a pilot.
Structure
-
Components are various classes that contain some business logic. Each component has a reference to a mediator, declared with the type of the mediator interface. The component isn’t aware of the actual class of the mediator, so you can reuse the component in other programs by linking it to a different mediator.
-
The Mediator interface declares methods of communication with components, which usually include just a single notification method. Components may pass any context as arguments of this method, including their own objects, but only in such a way that no coupling occurs between a receiving component and the sender’s class.
-
Concrete Mediators encapsulate relations between various components. Concrete mediators often keep references to all components they manage and sometimes even manage their lifecycle.
-
Components must not be aware of other components. If something important happens within or to a component, it must only notify the mediator. When the mediator receives the notification, it can easily identify the sender, which might be just enough to decide what component should be triggered in return.
From a component’s perspective, it all looks like a total black box. The sender doesn’t know who’ll end up handling its request, and the receiver doesn’t know who sent the request in the first place.
Pseudocode
In this example, the Mediator pattern helps you eliminate mutual dependencies between various UI classes: buttons, checkboxes and text labels.
Structure of the UI dialog classes.
An element, triggered by a user, doesn’t communicate with other elements directly, even if it looks like it’s supposed to. Instead, the element only needs to let its mediator know about the event, passing any contextual info along with that notification.
In this example, the whole authentication dialog acts as the mediator. It knows how concrete elements are supposed to collaborate and facilitates their indirect communication. Upon receiving a notification about an event, the dialog decides what element should address the event and redirects the call accordingly.
// The mediator interface declares a method used by components
// to notify the mediator about various events. The mediator may
// react to these events and pass the execution to other
// components.
interface Mediator is
method notify(sender: Component, event: string)
// The concrete mediator class. The intertwined web of
// connections between individual components has been untangled
// and moved into the mediator.
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegisterChkBx: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword
private field registrationEmail: Textbox
private field okBtn, cancelBtn: Button
constructor AuthenticationDialog() is
// Create all component objects and pass the current
// mediator into their constructors to establish links.
// When something happens with a component, it notifies the
// mediator. Upon receiving a notification, the mediator may
// do something on its own or pass the request to another
// component.
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "Log in"
// 1. Show login form components.
// 2. Hide registration form components.
else
title = "Register"
// 1. Show registration form components.
// 2. Hide login form components
if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// Try to find a user using login credentials.
if (!found)
// Show an error message above the login
// field.
else
// 1. Create a user account using data from the
// registration fields.
// 2. Log that user in.
// ...
// Components communicate with a mediator using the mediator
// interface. Thanks to that, you can use the same components in
// other contexts by linking them with different mediator
// objects.
class Component is
field dialog: Mediator
constructor Component(dialog) is
this.dialog = dialog
method click() is
dialog.notify(this, "click")
method keypress() is
dialog.notify(this, "keypress")
// Concrete components don't talk to each other. They have only
// one communication channel, which is sending notifications to
// the mediator.
class Button extends Component is
// ...
class Textbox extends Component is
// ...
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...
Applicability
Use the Mediator pattern when it’s hard to change some of the classes because they are tightly coupled to a bunch of other classes.
The pattern lets you extract all the relationships between classes into a separate class, isolating any changes to a specific component from the rest of the components.
Use the pattern when you can’t reuse a component in a different program because it’s too dependent on other components.
After you apply the Mediator, individual components become unaware of the other components. They could still communicate with each other, albeit indirectly, through a mediator object. To reuse a component in a different app, you need to provide it with a new mediator class.
Use the Mediator when you find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts.
Since all relations between components are contained within the mediator, it’s easy to define entirely new ways for these components to collaborate by introducing new mediator classes, without having to change the components themselves.
How to Implement
-
Identify a group of tightly coupled classes which would benefit from being more independent (e.g., for easier maintenance or simpler reuse of these classes).
-
Declare the mediator interface and describe the desired communication protocol between mediators and various components. In most cases, a single method for receiving notifications from components is sufficient.
This interface is crucial when you want to reuse component classes in different contexts. As long as the component works with its mediator via the generic interface, you can link the component with a different implementation of the mediator.
-
Implement the concrete mediator class. This class would benefit from storing references to all of the components it manages.
-
You can go even further and make the mediator responsible for the creation and destruction of component objects. After this, the mediator may resemble a factory or a facade.
-
Components should store a reference to the mediator object. The connection is usually established in the component’s constructor, where a mediator object is passed as an argument.
-
Change the components’ code so that they call the mediator’s notification method instead of methods on other components. Extract the code that involves calling other components into the mediator class. Execute this code whenever the mediator receives notifications from that component.
Pros and Cons
- Single Responsibility Principle. You can extract the communications between various components into a single place, making it easier to comprehend and maintain.
- Open/Closed Principle. You can introduce new mediators without having to change the actual components.
- You can reduce coupling between various components of a program.
-
You can reuse individual components more easily.
- Over time a mediator can evolve into a God Object.
Relations with Other Patterns
-
Chain of Responsibility, Command, Mediator and Observer address various ways of connecting senders and receivers of requests:
- Chain of Responsibility passes a request sequentially along a dynamic chain of potential receivers until one of them handles it.
- Command establishes unidirectional connections between senders and receivers.
- Mediator eliminates direct connections between senders and receivers, forcing them to communicate indirectly via a mediator object.
- Observer lets receivers dynamically subscribe to and unsubscribe from receiving requests.
-
Facade and Mediator have similar jobs: they try to organize collaboration between lots of tightly coupled classes.
- Facade defines a simplified interface to a subsystem of objects, but it doesn’t introduce any new functionality. The subsystem itself is unaware of the facade. Objects within the subsystem can communicate directly.
- Mediator centralizes communication between components of the system. The components only know about the mediator object and don’t communicate directly.
-
The difference between Mediator and Observer is often elusive. In most cases, you can implement either of these patterns; but sometimes you can apply both simultaneously. Let’s see how we can do that.
The primary goal of Mediator is to eliminate mutual dependencies among a set of system components. Instead, these components become dependent on a single mediator object. The goal of Observer is to establish dynamic one-way connections between objects, where some objects act as subordinates of others.
There’s a popular implementation of the Mediator pattern that relies on Observer. The mediator object plays the role of publisher, and the components act as subscribers which subscribe to and unsubscribe from the mediator’s events. When Mediator is implemented this way, it may look very similar to Observer.
When you’re confused, remember that you can implement the Mediator pattern in other ways. For example, you can permanently link all the components to the same mediator object. This implementation won’t resemble Observer but will still be an instance of the Mediator pattern.
Now imagine a program where all components have become publishers, allowing dynamic connections between each other. There won’t be a centralized mediator object, only a distributed set of observers.
Code Example
Mediator is a behavioral design pattern that reduces coupling between components of a program by making them communicate indirectly, through a special mediator object.
The Mediator makes it easy to modify, extend and reuse individual components because they’re no longer dependent on the dozens of other classes.
Usage of the pattern in Java
Complexity:
Popularity:
Usage examples: The most popular usage of the Mediator pattern in Java code is facilitating communications between GUI components of an app. The synonym of the Mediator is the Controller part of MVC pattern.
Here are some examples of the pattern in core Java libraries:
java.util.Timer
(allscheduleXXX()
methods)java.util.concurrent.Executor#execute()
java.util.concurrent.ExecutorService
(invokeXXX()
andsubmit()
methods)java.util.concurrent.ScheduledExecutorService
(allscheduleXXX()
methods)java.lang.reflect.Method#invoke()
Notes app
This example shows how to organize lots of GUI elements so that they cooperate with the help of a mediator but don’t depend on each other.
components: Colleague classes
components/Component.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
/**
* Common component interface.
*/
public interface Component {
void setMediator(Mediator mediator);
String getName();
}
components/AddButton.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import algamza.mediator.example.mediator.Note;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class AddButton extends JButton implements Component {
private Mediator mediator;
public AddButton() {
super("Add");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.addNewNote(new Note());
}
@Override
public String getName() {
return "AddButton";
}
}
components/DeleteButton.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class DeleteButton extends JButton implements Component {
private Mediator mediator;
public DeleteButton() {
super("Del");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.deleteNote();
}
@Override
public String getName() {
return "DelButton";
}
}
components/Filter.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import algamza.mediator.example.mediator.Note;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class Filter extends JTextField implements Component {
private Mediator mediator;
private ListModel listModel;
public Filter() {}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
String start = getText();
searchElements(start);
}
public void setList(ListModel listModel) {
this.listModel = listModel;
}
private void searchElements(String s) {
if (listModel == null) {
return;
}
if (s.equals("")) {
mediator.setElementsList(listModel);
return;
}
ArrayList<Note> notes = new ArrayList<>();
for (int i = 0; i < listModel.getSize(); i++) {
notes.add((Note) listModel.getElementAt(i));
}
DefaultListModel<Note> listModel = new DefaultListModel<>();
for (Note note : notes) {
if (note.getName().contains(s)) {
listModel.addElement(note);
}
}
mediator.setElementsList(listModel);
}
@Override
public String getName() {
return "Filter";
}
}
components/List.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import algamza.mediator.example.mediator.Note;
import javax.swing.*;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
@SuppressWarnings("unchecked")
public class List extends JList implements Component {
private Mediator mediator;
private final DefaultListModel LIST_MODEL;
public List(DefaultListModel listModel) {
super(listModel);
this.LIST_MODEL = listModel;
setModel(listModel);
this.setLayoutOrientation(JList.VERTICAL);
Thread thread = new Thread(new Hide(this));
thread.start();
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
public void addElement(Note note) {
LIST_MODEL.addElement(note);
int index = LIST_MODEL.size() - 1;
setSelectedIndex(index);
ensureIndexIsVisible(index);
mediator.sendToFilter(LIST_MODEL);
}
public void deleteElement() {
int index = this.getSelectedIndex();
try {
LIST_MODEL.remove(index);
mediator.sendToFilter(LIST_MODEL);
} catch (ArrayIndexOutOfBoundsException ignored) {}
}
public Note getCurrentElement() {
return (Note)getSelectedValue();
}
@Override
public String getName() {
return "List";
}
private class Hide implements Runnable {
private List list;
Hide(List list) {
this.list = list;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(300);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (list.isSelectionEmpty()) {
mediator.hideElements(true);
} else {
mediator.hideElements(false);
}
}
}
}
}
components/SaveButton.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class SaveButton extends JButton implements Component {
private Mediator mediator;
public SaveButton() {
super("Save");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.saveChanges();
}
@Override
public String getName() {
return "SaveButton";
}
}
components/TextBox.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.KeyEvent;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class TextBox extends JTextArea implements Component {
private Mediator mediator;
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}
@Override
public String getName() {
return "TextBox";
}
}
components/Title.java
package algamza.mediator.example.components;
import algamza.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.KeyEvent;
/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class Title extends JTextField implements Component {
private Mediator mediator;
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}
@Override
public String getName() {
return "Title";
}
}
mediator
mediator/Mediator.java: Defines common mediator interface
package algamza.mediator.example.mediator;
import algamza.mediator.example.components.Component;
import javax.swing.*;
/**
* Common mediator interface.
*/
public interface Mediator {
void addNewNote(Note note);
void deleteNote();
void getInfoFromList(Note note);
void saveChanges();
void markNote();
void clear();
void sendToFilter(ListModel listModel);
void setElementsList(ListModel list);
void registerComponent(Component component);
void hideElements(boolean flag);
void createGUI();
}
mediator/Editor.java: Concrete mediator
package algamza.mediator.example.mediator;
import algamza.mediator.example.components.*;
import algamza.mediator.example.components.Component;
import algamza.mediator.example.components.List;
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
/**
* Concrete mediator. All chaotic communications between concrete components
* have been extracted to the mediator. Now components only talk with the
* mediator, which knows who has to handle a request.
*/
public class Editor implements Mediator {
private Title title;
private TextBox textBox;
private AddButton add;
private DeleteButton del;
private SaveButton save;
private List list;
private Filter filter;
private JLabel titleLabel = new JLabel("Title:");
private JLabel textLabel = new JLabel("Text:");
private JLabel label = new JLabel("Add or select existing note to proceed...");
/**
* Here the registration of components by the mediator.
*/
@Override
public void registerComponent(Component component) {
component.setMediator(this);
switch (component.getName()) {
case "AddButton":
add = (AddButton)component;
break;
case "DelButton":
del = (DeleteButton)component;
break;
case "Filter":
filter = (Filter)component;
break;
case "List":
list = (List)component;
this.list.addListSelectionListener(listSelectionEvent -> {
Note note = (Note)list.getSelectedValue();
if (note != null) {
getInfoFromList(note);
} else {
clear();
}
});
break;
case "SaveButton":
save = (SaveButton)component;
break;
case "TextBox":
textBox = (TextBox)component;
break;
case "Title":
title = (Title)component;
break;
}
}
/**
* Various methods to handle requests from particular components.
*/
@Override
public void addNewNote(Note note) {
title.setText("");
textBox.setText("");
list.addElement(note);
}
@Override
public void deleteNote() {
list.deleteElement();
}
@Override
public void getInfoFromList(Note note) {
title.setText(note.getName().replace('*', ' '));
textBox.setText(note.getText());
}
@Override
public void saveChanges() {
try {
Note note = (Note) list.getSelectedValue();
note.setName(title.getText());
note.setText(textBox.getText());
list.repaint();
} catch (NullPointerException ignored) {}
}
@Override
public void markNote() {
try {
Note note = list.getCurrentElement();
String name = note.getName();
if (!name.endsWith("*")) {
note.setName(note.getName() + "*");
}
list.repaint();
} catch (NullPointerException ignored) {}
}
@Override
public void clear() {
title.setText("");
textBox.setText("");
}
@Override
public void sendToFilter(ListModel listModel) {
filter.setList(listModel);
}
@SuppressWarnings("unchecked")
@Override
public void setElementsList(ListModel list) {
this.list.setModel(list);
this.list.repaint();
}
@Override
public void hideElements(boolean flag) {
titleLabel.setVisible(!flag);
textLabel.setVisible(!flag);
title.setVisible(!flag);
textBox.setVisible(!flag);
save.setVisible(!flag);
label.setVisible(flag);
}
@Override
public void createGUI() {
JFrame notes = new JFrame("Notes");
notes.setSize(960, 600);
notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel left = new JPanel();
left.setBorder(new LineBorder(Color.BLACK));
left.setSize(320, 600);
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
JPanel filterPanel = new JPanel();
filterPanel.add(new JLabel("Filter:"));
filter.setColumns(20);
filterPanel.add(filter);
filterPanel.setPreferredSize(new Dimension(280, 40));
JPanel listPanel = new JPanel();
list.setFixedCellWidth(260);
listPanel.setSize(320, 470);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setPreferredSize(new Dimension(275, 410));
listPanel.add(scrollPane);
JPanel buttonPanel = new JPanel();
add.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(add);
del.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(del);
buttonPanel.setLayout(new FlowLayout());
left.add(filterPanel);
left.add(listPanel);
left.add(buttonPanel);
JPanel right = new JPanel();
right.setLayout(null);
right.setSize(640, 600);
right.setLocation(320, 0);
right.setBorder(new LineBorder(Color.BLACK));
titleLabel.setBounds(20, 4, 50, 20);
title.setBounds(60, 5, 555, 20);
textLabel.setBounds(20, 4, 50, 130);
textBox.setBorder(new LineBorder(Color.DARK_GRAY));
textBox.setBounds(20, 80, 595, 410);
save.setBounds(270, 535, 80, 25);
label.setFont(new Font("Verdana", Font.PLAIN, 22));
label.setBounds(100, 240, 500, 100);
right.add(label);
right.add(titleLabel);
right.add(title);
right.add(textLabel);
right.add(textBox);
right.add(save);
notes.setLayout(null);
notes.getContentPane().add(left);
notes.getContentPane().add(right);
notes.setResizable(false);
notes.setLocationRelativeTo(null);
notes.setVisible(true);
}
}
mediator/Note.java: A note’s class
package algamza.mediator.example.mediator;
/**
* Note class.
*/
public class Note {
private String name;
private String text;
public Note() {
name = "New note";
}
public void setName(String name) {
this.name = name;
}
public void setText(String text) {
this.text = text;
}
public String getName() {
return name;
}
public String getText() {
return text;
}
@Override
public String toString() {
return name;
}
}
Demo.java: Initialization code
package algamza.mediator.example;
import algamza.mediator.example.components.*;
import algamza.mediator.example.mediator.Editor;
import algamza.mediator.example.mediator.Mediator;
import javax.swing.*;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) {
Mediator mediator = new Editor();
mediator.registerComponent(new Title());
mediator.registerComponent(new TextBox());
mediator.registerComponent(new AddButton());
mediator.registerComponent(new DeleteButton());
mediator.registerComponent(new SaveButton());
mediator.registerComponent(new List(new DefaultListModel()));
mediator.registerComponent(new Filter());
mediator.createGUI();
}
}