Browse Design Patterns in Plain English: Demystifying Software Architecture

Practical Applications and Examples of the Mediator Pattern

Explore practical applications and examples of the Mediator Pattern in software architecture, focusing on user interface dialogs and widget interactions.

14.2.1 Practical Applications and Examples of the Mediator Pattern

The Mediator Pattern is a behavioral design pattern that facilitates communication between different components, often called colleagues, by introducing a mediator object. This pattern is particularly useful in scenarios involving complex interactions between multiple objects, such as in user interface dialogs where widgets like buttons and text fields need to interact seamlessly.

User Interface Dialog Example

Imagine a user interface dialog for a registration form containing several widgets: a text field for the username, a text field for the password, a submit button, and a cancel button. In a traditional setup, each widget might directly interact with others, leading to a tightly coupled system where changes to one widget necessitate changes to others. The Mediator Pattern helps avoid this by centralizing communication through a mediator object.

How the Mediator Handles Events

In our registration form example, the mediator manages the interaction logic. When a user enters a username, the mediator can enable the submit button only if both username and password fields are filled. Similarly, if the cancel button is clicked, the mediator can clear all fields and reset the form.

Here’s how it works:

  1. Widget Communication: Each widget communicates its changes to the mediator. For instance, when the username text field is updated, it notifies the mediator.
  2. Mediator Logic: The mediator contains the logic to determine the appropriate actions based on the changes. It checks whether to enable the submit button or perform other actions.
  3. Updating Widgets: The mediator then informs other widgets of the necessary updates. This decouples the widgets, as they do not need to know about each other’s existence.

Implementing the Mediator Pattern

To implement the Mediator Pattern, we need to define a Mediator interface, a Concrete Mediator class, and Colleague classes for each widget.

Mediator Interface

The Mediator interface defines the methods for communication between colleagues.

interface Mediator {
    void widgetChanged(Colleague colleague);
}

Concrete Mediator Class

The Concrete Mediator implements the Mediator interface and contains the logic for coordinating widget interactions.

class RegistrationFormMediator implements Mediator {
    private UsernameField usernameField;
    private PasswordField passwordField;
    private SubmitButton submitButton;
    private CancelButton cancelButton;

    public RegistrationFormMediator(UsernameField usernameField, PasswordField passwordField,
                                    SubmitButton submitButton, CancelButton cancelButton) {
        this.usernameField = usernameField;
        this.passwordField = passwordField;
        this.submitButton = submitButton;
        this.cancelButton = cancelButton;
    }

    @Override
    public void widgetChanged(Colleague colleague) {
        if (colleague == usernameField || colleague == passwordField) {
            updateSubmitButton();
        } else if (colleague == cancelButton) {
            clearFields();
        }
    }

    private void updateSubmitButton() {
        boolean enable = !usernameField.getText().isEmpty() && !passwordField.getText().isEmpty();
        submitButton.setEnabled(enable);
    }

    private void clearFields() {
        usernameField.setText("");
        passwordField.setText("");
        submitButton.setEnabled(false);
    }
}

Colleague Classes

Each widget is represented by a Colleague class that interacts with the mediator.

abstract class Colleague {
    protected Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    public void changed() {
        mediator.widgetChanged(this);
    }
}

class UsernameField extends Colleague {
    private String text = "";

    public UsernameField(Mediator mediator) {
        super(mediator);
    }

    public void setText(String text) {
        this.text = text;
        changed();
    }

    public String getText() {
        return text;
    }
}

class PasswordField extends Colleague {
    private String text = "";

    public PasswordField(Mediator mediator) {
        super(mediator);
    }

    public void setText(String text) {
        this.text = text;
        changed();
    }

    public String getText() {
        return text;
    }
}

class SubmitButton extends Colleague {
    private boolean enabled = false;

    public SubmitButton(Mediator mediator) {
        super(mediator);
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }
}

class CancelButton extends Colleague {
    public CancelButton(Mediator mediator) {
        super(mediator);
    }

    public void click() {
        changed();
    }
}

Best Practices for Using the Mediator Pattern

  • Clear and Well-Organized Logic: Keep the mediator’s logic simple and well-organized to prevent it from becoming a bottleneck in the system.
  • Decoupling Colleagues: Ensure that colleagues do not directly interact with each other, maintaining a clear separation of concerns.
  • Extending the Mediator: When adding new colleagues, update the mediator to handle new interactions. Consider using inheritance or composition to manage complexity.
  • Testing Interactions: Thoroughly test the interactions between colleagues to ensure the mediator coordinates them correctly.
  • Documenting Coordination Logic: Document the mediator’s logic for maintainability and ease of understanding by other developers.

Challenges and Strategies

One potential challenge with the Mediator Pattern is the risk of the mediator becoming overly complex as more colleagues are added. To address this, consider decomposing the mediator into smaller, more manageable components or using multiple mediators for different parts of the system.

Conclusion

The Mediator Pattern provides a robust solution for managing complex interactions in software systems, particularly in user interfaces. By centralizing communication through a mediator, it decouples components and simplifies maintenance. However, it requires careful design and testing to ensure the mediator remains efficient and manageable.

Quiz Time!

### What is the primary role of the mediator in the Mediator Pattern? - [x] To facilitate communication between colleagues - [ ] To store data for colleagues - [ ] To replace colleagues - [ ] To execute algorithms on behalf of colleagues > **Explanation:** The mediator's primary role is to facilitate communication between colleagues, ensuring they remain decoupled. ### How do widgets communicate changes in a system using the Mediator Pattern? - [x] They notify the mediator of any changes - [ ] They directly update each other - [ ] They use a shared global variable - [ ] They communicate through a central server > **Explanation:** Widgets notify the mediator of changes, which then coordinates updates to other widgets. ### In the provided code example, what triggers the mediator to update the submit button? - [x] Changes in the username or password fields - [ ] Clicking the submit button - [ ] Loading the form - [ ] Closing the form > **Explanation:** The mediator updates the submit button when changes occur in the username or password fields. ### Why is it important to decouple colleagues in the Mediator Pattern? - [x] To reduce dependencies and improve maintainability - [ ] To increase the complexity of the system - [ ] To ensure each colleague has a unique function - [ ] To make the mediator redundant > **Explanation:** Decoupling colleagues reduces dependencies and improves maintainability, making the system easier to manage. ### What is a potential risk when implementing the Mediator Pattern? - [x] The mediator becoming overly complex - [ ] Colleagues becoming too independent - [ ] The system running too efficiently - [ ] The mediator being unnecessary > **Explanation:** A potential risk is the mediator becoming overly complex, especially as more colleagues are added. ### How can the complexity of a mediator be managed? - [x] By decomposing it into smaller components - [ ] By adding more colleagues - [ ] By reducing the number of methods - [ ] By centralizing all logic in one place > **Explanation:** Decomposing the mediator into smaller components can help manage its complexity. ### What should be documented for maintainability in the Mediator Pattern? - [x] The mediator's coordination logic - [ ] The individual colleagues' methods - [ ] The user interface design - [ ] The server architecture > **Explanation:** Documenting the mediator's coordination logic is crucial for maintainability and understanding. ### What is the benefit of thoroughly testing interactions in the Mediator Pattern? - [x] Ensures the mediator coordinates correctly - [ ] Simplifies the mediator's logic - [ ] Reduces the need for a mediator - [ ] Increases the number of colleagues > **Explanation:** Thorough testing ensures the mediator coordinates correctly and that interactions function as expected. ### How does the Mediator Pattern affect the dependencies between colleagues? - [x] It reduces dependencies - [ ] It increases dependencies - [ ] It eliminates dependencies - [ ] It has no effect on dependencies > **Explanation:** The Mediator Pattern reduces dependencies by centralizing communication through a mediator. ### True or False: The Mediator Pattern is only useful for user interface design. - [ ] True - [x] False > **Explanation:** False. While it is often used in UI design, the Mediator Pattern is applicable in any scenario requiring decoupled communication between components.