JavaScript

Поскольку медиатор изменяет поведение программы и отвечает за координацию взаимодействий, он считается поведенческим шаблоном. По мере роста приложения возрастает и потребность в медиаторе из-за наличия нескольких объектов, напрямую взаимодействующих друг с другом.

Допустим, вы создали набор классов или функций для RPG-игры, которые не используют шаблон медиатор. Рассмотрим пример того, как это может выглядеть:

А теперь представьте, что вы пытаетесь прочитать и поддерживать лежащий в основе код.

Вам придется отслеживать все самостоятельно. Кошмар для разработчика!

При введении шаблона медиатор в этот код объект mediator размещается в середине, а его основная задача заключается в том, чтобы инкапсулировать все взаимодействия и поведения между ними и обрабатывать весь процесс самостоятельно.

Этот процесс выглядит следующим образом:

Мы видим, что объект mediator находится в середине и отвечает за взаимодействия между набором объектов.

Теперь представьте, что вы занимаетесь отладкой этого приложения. Если один из модулей выдает ошибку, единственное место, где она может находиться, это медиатор или сам модуль. В результате упрощается поддержка большой базы кода из-за более четкого разделения логики.

Более того, вы с легкостью сможете изменить что-либо в коде благодаря более точному представлению о направлении между объектами.


В каких случаях нужно использовать шаблон медиатор

Необходимость в этом шаблоне может возникнуть, когда какой-либо элемент в коде обладает слишком большим количеством прямых связей с другими частями.

В этом случае код будет нестабильным.

Этот шаблон обеспечивает слабую связанность между компонентами, гарантируя, что все взаимодействия будут скоординированы через центральную точку — медиатор.


Реальный пример

Рассмотрим реальный пример, чтобы разобраться в том, каким образом медиатор помогает повысить эффективность.

У учителя есть стопка бумаг, которые нужно раздать так, чтобы каждому ученику досталось по одному листку.

Вместо того, чтобы отдать пачку бумаг ученикам, чтобы каждый из них брал по одному листку и передавал ее дальше, учитель решает раздать бумаги самостоятельно.

Таким образом, он несет ответственность за то, чтобы каждый ребенок получил лист бумаги, играя роль медиатора. Затем, когда он доходит до последнего ученика, учащийся в другом конце комнаты поднимает руку и говорит, что не получил листок бумаги.

Попробуем «отладить» эту ситуацию. Сразу напрашивается вывод, что медиатор ответственен за недостающий листок бумаги, поскольку его роль состояла в том, чтобы предоставить каждому ученику лист бумаги.

Более того, у нас также есть точное предсказание того, где в данный момент находится стопка бумаги.

Если учитель передает стопку ученикам, то они будут ответственны за то, чтобы каждый получил лист бумаги. Ученик поднимает руку, говоря, что не получил бумагу, превращая «отладку» этой ситуации в непростую задачу.

Во-первых, нам не известно, где на данный момент находится стопка бумаг. Во-вторых, мы не знаем, кто нарушил процесс. Нам придется спрашивать каждого ученика, кому он передал пачку, чтобы выяснить, почему кто-то не получил листок.

Если вы занимаетесь веб-разработками на JavaScript, то уже работали с медиаторами.

В DOM примером медиатора является объект document, поскольку он может координировать логику и поведение между элементами DOM. Элемент input с type="radio" также может быть медиатором, поскольку он выбирает, какое радио проверить, и может содержать текущее значение.


Преимущества

При наличии нескольких объектов, которые напрямую взаимодействуют друг с другом, мы имеем дело с отношением «многие ко многим», настоящим кошмаром при отладке.

Медиатор поможет решить эту проблему путем введения потока «один ко многим» между объектами.

Другие преимущества включают уменьшение зависимостей между объектами (слабая связанность), упрощение ошибок и поддержки кода, а также улучшение читабельности.


Недостатки

К сожалению, не все в мире идеально. То же самое относится и к шаблону медиатор.

Например, наличие одной точки для координации всех взаимодействий между объектами означает, что при наличии ошибок будут затронуты все инкапсулированные элементы.

Причина заключается в том, что медиатор является центральной точкой взаимодействий между несколькими объектами, из-за чего могут возникнуть проблемы с производительностью при росте приложения, так как объекты связаны косвенно.

Однако если выбирать между тем, чтобы медиатор управлял взаимодействиями или объектами, взаимодействующими напрямую друг с другом, первый вариант в большинстве случаев будет лучшим выбором.

При внесении изменений в один из объектов и появлении исключения в неизвестном месте в остальной части приложения может возникнуть эффект домино.

Подобно примеру из реальной жизни, отладка этой ситуации становится настоящим кошмаром, поскольку необходимо проверить каждую точку связи в коде.


Пример кода

Представим, что мы создаем RPG-игру и для начала реализуем класс Novice и Party API.

Эта концепция предполагает объединение одной или нескольких групп пользователей вместе для создания команды союзников для совместного достижения определенной цели.

Нам потребуется определить конструктор Novice, а также Game и Party.

Novice используется для создания новых игроков, Party предоставляет API для функции party, а Game становится медиатором, который использует Party для создания связей между экземплярами Novice (пользователями).

Пример этого кода в действии:

function createId() {
  const S4 = function() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
  }
  
  return (
    S4() +
    S4() +
    '-' +
    S4() +
    '-' +
    S4() +
    '-' +
    S4() +
    '-' +
    S4() +
    S4() +
    S4()
  )
}

function Novice(name) {
  this.name = name
  this.hp = 100
  this.id = createId()
  this.party = null
}

Novice.prototype.attack = function(target, hp) {
  target -= hp
  return this
}

const roy = new Novice('roy')
const ben = new Novice('ben')
const lucy = new Novice('lucy')
const sally = new Novice('sally')

function Party(leader, ...members) {
  this.id = createId()
  this.leader = leader
  this.members = members
}

function Game(options) {
  this.parties = {}
  if (options) {
    // сделать что-то.
  }
}

Game.prototype.createParty = function(leader, ...members) {
  const party = new Party(leader, ...members)
  this.parties[party.id] = party
  leader.party = party
}

Game.prototype.removeParty = function(leader) {
  delete this.parties[leader.party.id]
  leader.party = null
}

const game = new Game()
game.createParty(roy, ben, lucy)

Как видно из примера, назначение экземпляров Game для координации связей party между несколькими пользователями значительно улучшает читабельность и упрощает поддержку кода, а также разделяет логику между экземплярами Party и Novice.

В противном случае логику необходимо реализовывать напрямую как часть интерфейса Novice или Party.

Вот и все! Надеемся, эта информация была для вас полезной и пригодится в будущем.

Читайте также:


Перевод статьи jsmanifest: The Mediator Pattern in JavaScript