Appearance
Mediator
Use a mediator object to encapsulate a series of object interactions. The Mediator pattern ensures that objects do not explicitly reference each other, thereby reducing coupling and allowing their interactions to be changed independently.
The Mediator pattern, also known as the Intermediary pattern, aims to simplify multi-party communication by transforming many-to-many relationships into simpler one-to-one relationships.
Some might immediately think of a real estate agent when hearing "mediator," which can be frustrating. However, this Mediator pattern is somewhat similar to a real estate agent's role, so let's set aside any frustrations and look at an example.
Example: Order Input System
Consider a simple order input system with the following four components:
- Checkbox list
- "Select All" button
- "Deselect All" button
- "Inverse Selection" button
The complexity arises because changes in the checkbox list affect the states (enabled/disabled) of the "Select All" and "Deselect All" buttons. When a user clicks a button like "Inverse Selection," it not only affects the checkbox states but may also impact the states of the "Select All" and "Deselect All" buttons. This results in multi-party interactions, making the logic complex to implement.
Without Mediator
┌─────────────────┐ ┌─────────────────┐
│ CheckBox List │◀───▶│SelectAll Button │
└─────────────────┘ └─────────────────┘
▲ ▲ ▲
│ └─────────────────────┤
▼ │
┌─────────────────┐ ┌────────┴────────┐
│SelectNone Button│◀────│ Inverse Button │
└─────────────────┘ └─────────────────┘
Implementing interactions directly among these components results in a tangled web of dependencies.
With Mediator
Introducing a mediator simplifies the relationships by turning multi-party interactions into multiple one-to-one interactions through a central mediator:
┌─────────────────┐
┌─────▶│ CheckBox List │
│ └─────────────────┘
│ ┌─────────────────┐
│ ┌───▶│SelectAll Button │
▼ ▼ └─────────────────┘
┌─────────┐
│Mediator │
└─────────┘
▲ ▲ ┌─────────────────┐
│ └───▶│SelectNone Button│
│ └─────────────────┘
│ ┌─────────────────┐
└─────▶│ Inverse Button │
└─────────────────┘
By introducing a mediator, we gain the following benefits:
- Loose Coupling: UI components no longer reference each other directly, reducing interdependencies.
- Simplified Communication: The mediator handles the logic for updating component states based on interactions, making the system easier to manage and extend.
- Ease of Extension: Adding new UI components requires only updates to the mediator's logic without altering existing components.
Implementing the Mediator Pattern
Let's implement the Mediator pattern for the order input system with UI components.
Step 1: Define the Main Class
java
public class Main {
public static void main(String[] args) {
new OrderFrame("Hamburger", "Nugget", "Chip", "Coffee");
}
}
Step 2: Create the OrderFrame Class
java
class OrderFrame extends JFrame {
public OrderFrame(String... names) {
setTitle("Order");
setSize(460, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = getContentPane();
c.setLayout(new FlowLayout(FlowLayout.LEADING, 20, 20));
c.add(new JLabel("Use Mediator Pattern"));
List<JCheckBox> checkboxList = addCheckBox(names);
JButton selectAll = addButton("Select All");
JButton selectNone = addButton("Select None");
selectNone.setEnabled(false);
JButton selectInverse = addButton("Inverse Select");
new Mediator(checkboxList, selectAll, selectNone, selectInverse);
setVisible(true);
}
private List<JCheckBox> addCheckBox(String... names) {
JPanel panel = new JPanel();
panel.add(new JLabel("Menu:"));
List<JCheckBox> list = new ArrayList<>();
for (String name : names) {
JCheckBox checkbox = new JCheckBox(name);
list.add(checkbox);
panel.add(checkbox);
}
getContentPane().add(panel);
return list;
}
private JButton addButton(String label) {
JButton button = new JButton(label);
getContentPane().add(button);
return button;
}
}
Step 3: Design the Mediator Class
java
public class Mediator {
// References to UI components:
private List<JCheckBox> checkBoxList;
private JButton selectAll;
private JButton selectNone;
private JButton selectInverse;
public Mediator(List<JCheckBox> checkBoxList, JButton selectAll, JButton selectNone, JButton selectInverse) {
this.checkBoxList = checkBoxList;
this.selectAll = selectAll;
this.selectNone = selectNone;
this.selectInverse = selectInverse;
// Bind events:
this.checkBoxList.forEach(checkBox -> {
checkBox.addChangeListener(this::onCheckBoxChanged);
});
this.selectAll.addActionListener(this::onSelectAllClicked);
this.selectNone.addActionListener(this::onSelectNoneClicked);
this.selectInverse.addActionListener(this::onSelectInverseClicked);
}
// When a checkbox changes:
public void onCheckBoxChanged(ChangeEvent event) {
boolean allChecked = true;
boolean allUnchecked = true;
for (var checkBox : checkBoxList) {
if (checkBox.isSelected()) {
allUnchecked = false;
} else {
allChecked = false;
}
}
selectAll.setEnabled(!allChecked);
selectNone.setEnabled(!allUnchecked);
}
// When "Select All" is clicked:
public void onSelectAllClicked(ActionEvent event) {
checkBoxList.forEach(checkBox -> checkBox.setSelected(true));
selectAll.setEnabled(false);
selectNone.setEnabled(true);
}
// When "Select None" is clicked:
public void onSelectNoneClicked(ActionEvent event) {
checkBoxList.forEach(checkBox -> checkBox.setSelected(false));
selectAll.setEnabled(true);
selectNone.setEnabled(false);
}
// When "Inverse Selection" is clicked:
public void onSelectInverseClicked(ActionEvent event) {
checkBoxList.forEach(checkBox -> checkBox.setSelected(!checkBox.isSelected()));
onCheckBoxChanged(null);
}
}
Step 4: Run the Application
When you run the application, you should see the following behavior:
- Select All Button: Enables all checkboxes and disables itself while enabling the "Deselect All" button.
- Deselect All Button: Disables all checkboxes and enables the "Select All" button while disabling itself.
- Inverse Selection Button: Toggles the selection state of each checkbox and updates the states of the "Select All" and "Deselect All" buttons accordingly.
Benefits of Using the Mediator Pattern
- Reduced Coupling: UI components do not need to reference each other directly, leading to a more modular and maintainable codebase.
- Centralized Control: The mediator manages the interactions and dependencies, making it easier to understand and modify the communication logic.
- Ease of Extension: Adding new components or modifying existing interactions requires changes only in the mediator, not in the individual components.
Common Applications
The Mediator pattern is often used in scenarios with numerous interacting components, especially in UI systems. Frameworks like MVC (Model-View-Controller) and MVVM (Model-View-ViewModel) can be seen as extensions of the Mediator pattern, where the controller or view model acts as a mediator between the view and the model.
Exercise
Implement the Mediator pattern for a simple order input system with multiple UI components interacting through a mediator.
Summary
The Mediator pattern uses a mediator object to encapsulate and manage the interactions among multiple objects, converting many-to-many relationships into simpler one-to-one relationships. This reduces the coupling between system components and simplifies their communication, making the system more maintainable and extensible.