Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Created February 15, 2012 17:01
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save addyosmani/1837327 to your computer and use it in GitHub Desktop.
Save addyosmani/1837327 to your computer and use it in GitHub Desktop.
Mediators

The dictionary refers to a Mediator as 'a neutral party who assists in negotiations and conflict resolution'.

In software engineering, a Mediator is a behavioural design pattern that allow us to expose a unified interface through which the different parts of a system may communicate. If it appears a system may have too many direct relationships between modules, it may be time to have a central point of control that modules communicate through instead. The Mediator promotes loose coupling by ensuring that instead of modules referring to each other explicitly, their interaction is handled through this central point.

If you would prefer an analogy, consider a typical airport traffic control system. A tower (Mediator) handles what planes (modules) can take off and land because all communications are done from the planes to the control tower, rather than from plane-to-plane. A centralized controller is key to the success of this system and that's really the role a mediator plays in software design.

In real-world terms, a mediator encapsulates how disparate modules interact with each other by acting as an intermediary. At it's most basic, a mediator could be implemented as a central base for accessing functionality as follows:


// Our app namespace can act as a mediator
var app = app || {};

// Communicate through the mediator for Ajax requests
app.sendRequest = function ( options ) {
    return $.ajax($.extend({}, options);
}

// When a request for a URL resolves, do something with the view
app.populateView = function( url, view ){
  $.when(app.sendRequest({url: url, method: 'GET'})
     .then(function(){
         //populate the view
     });
}

// Empty a view of any content it may contain
app.resetView = function( view ){
   view.html('');
}

That said, in the JavaScript world it's become quite common for the Mediator to act as a messaging bus on top of the Observer-pattern. Rather than modules calling a Publish/Subscribe implementation, they'll use a Mediator with these capabilities built in instead. A possible implementation of this (based on work by Ryan Florence) could look as follows:

var mediator = (function(){
    // Subscribe to an event, supply a callback to be executed 
    // when that event is broadcast
    var subscribe = function(channel, fn){
        if (!mediator.channels[channel]) mediator.channels[channel] = [];
        mediator.channels[channel].push({ context: this, callback: fn });
        return this;
    },

    // Publish/broadcast an event to the rest of the application
    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;
        }
    };

}());

Here are two sample uses of the implementation from above. It's effectively centralized Publish/Subscribe where a mediated implementation of the Observer pattern is used:


(function(Mediator){

 function initialize(){

   // Set a default value for 'name'
   mediator.name = "tim";

   // Subscribe to an event called 'nameChange' with
   // a callback function which will log the original
  // name and (if everything works) the new name

  mediator.subscribe('nameChange', function(arg){
        console.log(this.name);
        this.name = arg;
        console.log(this.name);
   });
 }

 function updateName(){
   // Publish/Broadcast the 'nameChange' event with the new data
   mediator.publish('nameChange', 'david'); // tim, david
 }

})(mediator);

##Advantages & Disadvantages

The benefits of the Mediator pattern are that that it simplifies object interaction and can aid with decoupling those using it as a communication hub. In the above example, rather than using the Observer pattern to explicitly set many-to-many listeners and events, a Mediator allows you to broadcast events globally between subscribers and publishers. Broadcasted events can be handled by any number of modules at once and a mediator can used for a number of other purposes such as permissions management, given that it can control what messages can be subscribed to and which can be broadcast.

Perhaps the biggest downside of using the Mediator pattern is that it can introduce a single point of failure. Placing a Mediator between modules can also cause a performance hit as they are always communicating indirectly.Because of the nature of loose coupling, its difficult to establish how a system might react by only looking at the broadcasts. That said, it's useful to remind ourselves that decoupled systems have a number of other benefits - if our modules communicated with each other directly, changes to modules (e.g another module throwing an exception) could easily have a domino effect on the rest of your application. This problem is less of a concern with decoupled systems.

At the end of the day, tight coupling causes all kinds of headaches and this is just another alternative solution, but one which can work very well if implemented correctly.

##Mediator Vs. Observer

Developers often wonder what the differences are between the Mediator pattern and the Observer pattern. Admittedly, there is a bit of overlap, but let's refer back to the GoF for an explanation:

"In the Observer pattern, there is no single object that encapsulates a constraint. Instead, the Observer and the Subject must cooperate to maintain the constraint. Communication patterns are determined by the way observers and subjects are interconnected: a single subject usually has many observers, and sometimes the observer of one subject is a subject of another observer."

The Mediator pattern centralizes rather than simply just distributing. It places the responsibility for maintaining a constraint squarely in the mediator.

##Mediator Vs. Facade

Some developers may also wonder whether there are similarities between the Mediator and Facade patterns. They do both abstract the functionality of existing modules, but there are some subtle differences.

The Mediator centralizes communication between colleagues (modules) where it's explicitly referenced by these modules. In a sense this is multidirectional. The Facade however just defines a simpler interface to a module or system but doesn't add any additional functionality. Other modules in the system aren't directly aware of the concept of a facade and could be considered unidirectional.

@addyosmani
Copy link
Author

The mediator has been described in the GoF book, Design Patterns: Elements of Reusable Object-Oriented Software as a behavioural pattern with the following goal: "Define an object that encapsulated how a set of objects interact. The Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently."

The benefits of this pattern are that that it simplifies object interaction and can aid with decoupling those using it as a communication hub. Instead of using the Observer pattern to explicitly set many-to-many listeners and events, a Mediator allows you to broadcast events globally between subscribers and publishers. Perhaps the biggest downside of using the Mediator pattern is that it can introduce a single point of failure and can in some cases become difficult to manage effectively.

@addyosmani
Copy link
Author

Note: some developers use a variation of the mediator, where there is no concept of publish/subscribe, but instead simply a wrapper interface around methods such as:

app.sendRequest = function ( options ) {
    return $.ajax($.extend({}, options);
}

I would consider this more of a facade personally, but worth documenting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment