JavaScript Design Patterns: Mediator
The Mediator Design Pattern
The Mediator is a behavioral design pattern in which objects, instead of communicating directly with each other, communicate only through a central message passing object (the Mediator). The Mediator pattern facilitates both loose couple and high cohesion.
Loose Coupling
Coupling measures the degree to which program modules rely on other modules. Loose coupling implies each component can operate or be tested independently of other components. Tight coupling implies each component "knows" the details or inner workings of other components. The Mediator almost entirely eliminates coupling between participating objects by becoming the only outside point of contact. All a module needs to know is how to broadcast messages through the Mediator — it doesn't matter whether 0, 1, or even 100 other modules act on those messages.
- What's good about loose coupling? Loose coupling facilitates rapid prototyping by getting rid of code dependencies. Components can broadcast and/or listen for messages without worrying about the rest of the system. Messages can be completely ignored, or handled by any number of other components at once. You can add and remove listening objects without changing anything about how those messages are broadcast. It is typically much easier to add new features to software when its components are loosely coupled.
- What's bad about loose coupling? By inserting
the Mediator between objects, components always communicate
indirectly, which may cause a very slight
performance hit. Also, due to the very definition of
loose coupling, there's no way to tell how the system might
react to a message by only looking at the point of broadcast.
For some, this may take a shift in thinking. This is actually
good, though — If components call each other directly,
for example using
myObject.someFunction(args), then one change in how your program works may cause a ripple effect of changes through several modules. TL;DR: tight coupling causes headaches
High Cohesion
Cohesion is a measure of how focused a piece of code is. High cohesion implies a component's properties and methods are strongly related, and work toward one or more closely related tasks. Low cohesion implies a component may handle two or more unrelated or dissimilar tasks.
- What's good about high cohesion? Lower maintenance cost, whether that means money, time, stress, or some combination thereof. Software modifications tend not to affect other parts of the program, which means they can be more easily and with more confidence. Reading and understanding your code also becomes easier when related tasks are grouped. High cohesion also facilitates code reuse, which again saves time and money. If you need to perform the same task in another application, you know where to go for the solution, and you know unrelated or unnecessary code won't "tag along".
- What's bad about high cohesion? I could come up with no obvious downsides to high cohesion — if you know any, please leave a comment for the sake of completeness!
The following example was built on ideas presented by Paul Marcotte. I especially like his comparison between the Mediator pattern and the Observer pattern:
"Instead of using the Observer pattern to explicitly set many-to-many listeners and events, Mediator allows you to broadcast events globally across colleagues."
Mediator = function() {
var debug = function() {
// console.log or air.trace as desired
};
var components = {};
var broadcast = function(event, args, source) {
var e = event || false;
var a = args || [];
if (!e) {
return;
}
//debug(["Mediator received", e, a].join(' '));
for (var c in components) {
if (typeof components[c]["on" + e] == "function") {
try {
//debug("Mediator calling " + e + " on " + c);
var s = source || components[c];
components[c]["on" + e].apply(s, a);
} catch (err) {
debug(["Mediator error.", e, a, s, err].join(' '));
}
}
}
};
var addComponent = function(name, component, replaceDuplicate) {
if (name in components) {
if (replaceDuplicate) {
removeComponent(name);
} else {
throw new Error('Mediator name conflict: ' + name);
}
}
components[name] = component;
};
var removeComponent = function(name) {
if (name in components) {
delete components[name];
}
};
var getComponent = function(name) {
return components[name] || false;
};
var contains = function(name) {
return (name in components);
};
return {
name : "Mediator",
broadcast : broadcast,
add : addComponent,
rem : removeComponent,
get : getComponent,
has : contains
};
}();
And here's how to use it:
Mediator.add('TestObject', function() {
var someNumber = 0; // sample variable
var someString = 'another sample variable';
return {
onInitialize: function() {
// this.name is automatically assigned by the Mediator
alert(this.name + " initialized.");
},
onFakeEvent: function() {
someNumber++;
alert("Handled " + someNumber + " times!");
},
onSetString: function(str) {
someString = str;
alert('Assigned ' + someString);
}
}
}());
Mediator.broadcast("Initialize"); // alerts "TestObject initialized"
Mediator.broadcast('FakeEvent'); // alerts "Handled 1 times!" (I know, bad grammar)
Mediator.broadcast('SetString', ['test string']); // alerts "Assigned test string"
Mediator.broadcast('FakeEvent'); // alerts "Handled 2 times!"
Mediator.broadcast('SessionStart'); // this call is safely ignored
Mediator.broadcast('Translate', ['this is also safely ignored']);







0 Comments