Объяснить, что представляет собой паттерн «медиатор» достаточно просто на примере следующей аналогии — представьте себе контроль трафика в аэропорту: все решения о том, какие самолеты могут взлетать или садиться, принимает диспетчер. Для этого, все сообщения, исходящие от самолетов, поступают в башню управления, вместо того, чтобы пересылаться между самолетами напрямую. Такой централизованный контроллер — это и есть ключ к успеху нашей системы. Это и есть «медиатор».
Медиатор выступает в качестве посредника в общении между различными модулями, инкапсулируя их взаимодействие. Кроме того, этот шаблон проектирования, предотвращая прямое взаимодействие различных компонентов системы, способствует ослаблению связей в коде. В нашей системе он также помогает в решении проблем, связанных с зависимостями модулей.
Какие еще преимущества существуют у «медиатора»? К примеру, медиатор позволяет каждому модулю функционировать абсолютно независимо от других компонентов системы, что приводит к большей гибкости. Если вам ранее уже приходилось использовать паттерн «наблюдатель» в роли системы доставки событий между различными частями в вашей системе, то вам не составит труда разобраться с медиатором.
Давайте посмотрим на модель взаимодействия модулей и медиатора:
Мы можем рассматривать модули, как «издателей», публикующих события. Медиатор же
является и «издателем» и «подписчиком» одновременно. В примере, Module 1
посылает
сообщение, предполагающее некоторую реакцию, медиатору. Затем, медиатор, получив
сообщение, уведомляет другие модули об определенных действиях, которые необходимо
выполнить для завершения задачи. Module 2
выполняет необходимые Module 1
действия, и сообщает о результате обратно, в медиатор. В это же время, медиатор
запускает Module 3
для логгирования поступающих сообщений.
Обратите внимание: здесь нет прямого взаимодействия между модулями. Если
в Module 3
произойдет ошибка или, к примеру, он просто перестанет работать,
то медиатор, теоретически, может приостановить выполнение задач в других модулях,
затем перезапустить Module 3
и продолжить работу, практически не влияя на работу
всей системы. Такая слабая связанность модулей является одним из самых сильных
преимуществ паттерна «медиатор», который я вам предлагаю использовать.
Посмотрим на его преимущества:
Уменьшает связывание модулей, добавляя посредника — центральный элемент управления. Это позволяет модулям отправлять и слушать сообщения, не затрагивая остальной части системы. Сообщения могут быть обработаны любым количеством модулей сразу.
Благодаря слабой связанности кода, внедрение новой функциональности происходит существенно легче.
И недостатки:
Модули больше не могут взаимодействовать напрямую. Использование медиатора приводит к небольшому падению производительности — такова природа слабой связанности — становится достаточно трудно определить реакцию системы, отталкиваясь только от событий, происходящих в ней.
Наконец, системы с высокой связанностью кода являются обычно источником всевозможных проблем, решением которых может стать уменьшение связанности.
Пример: одна из возможных реализаций паттерна «медиатор», основанная на работе @rpflorence
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;
}
};
}());
И два примера использования реализации, написанной выше:
//Pub/sub on a centralized mediator
mediator.name = "tim";
mediator.subscribe('nameChange', function(arg) {
console.log(this.name);
this.name = arg;
console.log(this.name);
});
mediator.publish('nameChange', 'david'); //tim, david
//Pub/sub via third party mediator
var obj = {name: 'sam'};
mediator.installTo(obj);
obj.subscribe('nameChange', function(arg) {
console.log(this.name);
this.name = arg;
console.log(this.name);
});
obj.publish('nameChange', 'john'); //sam, john