Mediator Pattern

The pattern allows:

  • loose coupling by encapsulating the way disparate sets of objects interact and communicate with each other;
  • to maintain an application architecture where too many relationships exist and common point of control communication is required.

Mediator Pattern


With the Mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.

PHP Example

Let us assume, we have a career site. As candidates apply we store their job applications. HR assistants change application status according to defined workflow. When a job application gets the status ‘Candidate is hired’, the number of open vacancies for the job position must be decremented. We could just call JobPosition model from within update method of JobApplication model. But department managers want to be notified when candidates are hired. Later can come new requirements. So, we will have mediator object notifying any number of the objects interested in the event.

<?php
/*
 * @category Design Pattern Tutorial
 * @package Mediator Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */

class Model_JobApplication
{
    const STATUS_HIRED = 7;
    private $_mediator;

    public function  __construct(Model_Mediator_Interface $mediator = null)
    {
        $this->_mediator = $mediator;
    }

    public function updateStatus($id, $newStatusId)
    {
        if (!is_null($this->_mediator)) {
            $this->_mediator->update($this, array(
                "statusId" => $newStatusId,
                "appId" => $id,
                "posId" => 1 // TILT
                ));
        }
    }
}
class Model_JobPosition
{
    public function updateAsStatusChanges($data)
    {
        if ($data['statusId'] === Model_JobApplication::STATUS_HIRED) {
            echo 'Number of open vacancies decremented', "\n";
        }
    }
}
class Model_JobPositionObserver
{
    // Observers and job positions on which they are subscibed for status updates
    private $_subscription = array(
        'department1@company.com' => array(1 => array(Model_JobApplication::STATUS_HIRED)),
        'department2@company.com' => array(1 => array(Model_JobApplication::STATUS_HIRED))
    );

    public function updateAsStatusChanges($data)
    {
        foreach ($this->_subscription as $mail => $positions) {
            if (isset ($positions[$data['posId']])
                && in_array($data['statusId'], $positions[$data['posId']])) {
                echo $mail . ' notified', "\n";
            }
        }
    }
}
interface Model_Mediator_Interface
{
    public function update($origObject, $data);
}
class Model_Mediator_JobApplicationStatus implements Model_Mediator_Interface
{
    private $_subscribers = array('JobPosition', 'JobPositionObserver');
    public function update($origObject, $data)
    {
        foreach ($this->_subscribers as $subscriber) {
            $subscriberClass = 'Model_' . $subscriber;
            if (!($origObject instanceof $subscriberClass)) {
                $object = new $subscriberClass();
                $object->updateAsStatusChanges($data);
            }
        }
    }
}

/**
 * Usage
 */

$model = new Model_JobApplication(new Model_Mediator_JobApplicationStatus());
$id = 1;
$newStatusId = Model_JobApplication::STATUS_HIRED;
$model->updateStatus($id, $newStatusId);

// Output:
// Number of open vacancies decremented
// department1@company.com notified
// department2@company.com notified

JS/ES5 Example

JS is very good for building Event-driven architecture. Here in the example we have widgets communication through the mediator using PubSub communication pattern. The module talking to others just publishes an event attaching the data to it. All the modules subscribed to the event will be notified and received the sent data.

/*
 * @category Design Pattern Tutorial
 * @package Mediator Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {

// Mediator by Addy Osmani
var mediator = (function(){
    var subscribe = function(channel, fn){
        if (!mediator.channels[channel]) mediator.channels[channel] = [];
        mediator.channels[channel].push({ context: this, callback: fn });
        return this;
    },

    publish = function(channel){
        if (!mediator.channels[channel]) return false;
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
            var subscription = mediator.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }
        return this;
    };

    return {
        channels: {},
        publish: publish,
        subscribe: subscribe,
        installTo: function(obj){
            obj.subscribe = subscribe;
            obj.publish = publish;
        }
    };

}());

var ForgetPasswordFormWidget = (function() {
    var _handler = {
        AuthServicesLocked : function(data) {
            console.log('Forget password form locked');
        },
        AuthServicesUnlocked: function(data) {
            console.log('Forget password form unlocked');
        }
    }
    subscribe('AuthServicesLocked', _handler.AuthServicesLocked );
    subscribe('AuthServicesUnlocked', _handler.AuthServicesUnlocked);
    return {

    }
}());
mediator.installTo(ForgetPasswordFormWidget);

var LoginFormWidget = (function() {
    return {
        login : function() {
            // Imagine, it sends here request to the server
            console.log('Login form locked');
            this.publish('AuthServicesLocked', {});
            console.log('Login form unlocked');
            this.publish('AuthServicesUnlocked', {});
        }
    }
}());
mediator.installTo(LoginFormWidget);

/**
 * Usage
 */
LoginFormWidget.login();

// Output
// Login form locked
// Forget password form locked
// Login form unlocked
// Forget password form unlocked

}());