Skip to main content

Mediator

complexitypopularity

Ban components from talking to each other directly and route every interaction through one mediator object, so nobody depends on anybody except the hub.

The problem

You have a dialog for editing customer profiles: text fields, checkboxes, buttons. Some elements react to others. Ticking "I have a dog" reveals a field for the dog's name. The submit button has to validate every field before saving.

Put that logic inside the elements and the elements stop being elements. Your checkbox class now holds a reference to a specific text field, so it cannot appear in any other form. You can reuse the entire profile screen or none of it.

Each new relationship is another wire in a growing cat's cradle, and every wire is a reason a class cannot move.

The solution

Cut all direct communication between the components you want independent. They now collaborate indirectly, by calling one mediator object that redirects to whoever should react. Each component depends on a single class instead of a dozen colleagues.

In the profile form the dialog itself makes a natural mediator, since it already knows about its own children. The change lands on the components. The submit button used to validate every field; now its whole job is to tell the dialog it was clicked. The dialog does the validating, or delegates it.

Then extract a mediator interface with the notification method. Now the button works with any dialog implementing it, and the component becomes genuinely portable. Fewer dependencies per class, easier to change, extend, and reuse.

UserCheckboxAuthenticationDialogRegistration TextboxOK Buttoncheck()1notify(this, "check")2sender == chkBx && event == "check"3show()4click()5notify(this, "click")6read values, create account7
  1. 1The user flips the "I want to register" checkbox. So far, an ordinary widget doing an ordinary thing.
  2. 2The checkbox reports the event and its own identity to the mediator. That is the entirety of its social life.
  3. 3The mediator identifies the sender and looks up the rule. All the coupling that used to be spread across widgets sits in this one method.
  4. 4The mediator reveals the registration fields and hides the login ones. The checkbox never learned that these fields exist.
  5. 5The user hits OK. The button does not validate anything - it has no references with which to validate.
  6. 6Same single channel, different sender. The mediator now knows it is in registration mode and which fields to read.
  7. 7The mediator orchestrates the finish. Reuse this Button in a settings form tomorrow: give it a different mediator, change nothing else.

Structure

The important constraint is negative: components must not know about other components. When something happens, a component notifies the mediator and nothing else. From inside a component, the rest of the system is an opaque box - the sender does not know who will handle its news, and the receiver does not know who caused it.

«interface»Mediatornotify(sender: Component, event: string)MEDIATOR«abstract»Componentdialog: Mediatorclick()keypress()BASEAuthenticationDialogtitle: stringloginOrRegisterChkBx: CheckboxloginUsername, loginPassword: TextboxokBtn, cancelBtn: Buttonnotify(sender, event)CONCRETEButton(inherits click)CONCRETETextbox(inherits keypress)CONCRETECheckboxchecked: boolcheck()CONCRETE
implementsextendsuses

Code

Same example three ways: an authentication dialog that flips between login and registration, acting as its own mediator.

// Every widget reaches into every other widget.
class RegisterCheckbox extends Checkbox is
// Coupled to three specific fields on one specific form.
field dogNameField: Textbox
field emailField: Textbox
field usernameField: Textbox
 
method check() is
if (this.checked)
dogNameField.show()
emailField.show()
else
dogNameField.hide()
emailField.hide()
 
class SubmitButton extends Button is
// Coupled to every field it has to validate.
field username: Textbox
field password: Textbox
field email: Textbox
 
method click() is
if (username.value == "" or password.value == "")
errorLabel.setText("Fill in the required fields")
return
// ...
 
// Want the checkbox in a different form? You cannot take it alone.
// It arrives with three text fields attached, or not at all.
// The checkbox reports an event and stops thinking.
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
 
// The rule that used to live inside the checkbox now lives in the hub,
// alongside every other rule, where you can read them all at once.
class AuthenticationDialog implements Mediator is
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
// Show one set of fields, hide the other.
 
// The payoff: this Checkbox has zero references to other widgets.
// Drop it in any form, hand it that form's mediator, done.
// The mediator interface: components use it to report events. The
// mediator may react itself or hand the work to another component.
interface Mediator is
method notify(sender: Component, event: string)
 
 
// The concrete mediator. Every relationship that used to be strung
// between components has been pulled in here.
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 the components and pass this mediator into each of
// their constructors to establish the links.
 
// One inbox for the whole dialog. Identify the sender, look up
// the rule, drive whichever components need to move.
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 the registration fields.
// 2. Log that user in.
 
 
// Components talk to the mediator through the interface, which is what
// makes them droppable into a different form with a different mediator.
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 never address each other. One outbound channel,
// and it points at the mediator.
class Button extends Component is
// ...
 
class Textbox extends Component is
// ...
 
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...

When to use it

  • Some classes are painful to change because they are welded to a crowd of other classes. Mediator lifts the relationships out into one place, so a change to one component stops rippling.
  • A component cannot be reused elsewhere because it depends on its neighbors. Give it a new mediator instead of new neighbors.
  • You are breeding component subclasses purely to reuse basic behavior in different contexts. New collaboration rules should mean a new mediator, not new components.

Pitfalls

  • The God Object. Every rule you remove from a component lands in the mediator. Left unchecked it becomes the class nobody dares edit. Split by concern before that happens.
  • Coupling relocated, not deleted. The system is still exactly as interconnected; you have just made the connections visible and central. That is usually worth it - but do not mistake it for simplification.
  • Anemic components. Push too hard and widgets become dumb event emitters with no behavior worth naming, and every trivial interaction takes a round trip through the hub.
  • Stringly typed events. notify(sender, "chekc") compiles fine and does nothing. Enums or typed events cost little and catch this at build time.

Don't confuse it with

  • Observer. The elusive one. Mediator's goal is eliminating mutual dependencies among peers by making them all depend on a hub; Observer's goal is dynamic one-way subscriptions where some objects are subordinate to others. They blur because a popular Mediator implementation uses Observer internally - the hub is the publisher, the components are subscribers. But you can also permanently wire every component to one mediator, which looks nothing like Observer and is still Mediator. Push the other direction, make every component a publisher with dynamic links, and you have no mediator at all - just a distributed mesh of observers.
  • Facade. Both organize collaboration among tightly coupled classes. A Facade offers a simplified interface to a subsystem without adding functionality; the subsystem does not know the facade exists and its objects still call each other directly. A Mediator centralizes communication and its components have no other channel.
  • Command. Command keeps the sender-to-receiver link and packages it as an object. Mediator deletes the link outright.
  • Chain of Responsibility. CoR arranges receivers in a line the request travels along; Mediator puts one object in the middle that every message must pass through.

Check yourself

Question 1 of 5

After applying Mediator, what does a Button know about the rest of the form?