Также известен как Intermediary, Controller, Mediator

Посредник

Суть паттерна

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

Проблема

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

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

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

Решение

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

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

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

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

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

Аналогия из жизни

Диспетчерская башня в аэропорту

Пилоты садящихся или улетающих самолётов не общаются напрямую с другими пилотами. Вместо этого, они связываются с диспетчером, который координирует действия нескольких самолётов одновременно. Без диспетчера, пилотам приходилось бы все время быть начеку и следить за всеми окружающими самолётами самостоятельно. А это приводило бы к частым катастрофам в небе.

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

Структура

Схема структуры классов паттерна Посредник
  1. Компоненты — это разнородные объекты, содержащие бизнес-логику программы. Каждый компонент хранит ссылку на объект посредника, но работает с ним только через общий интерфейс Посредников. Они не знают с каким именно классом посредников работают. Благодаря этому, компоненты можно повторно использовать в другой программе, заменив посредника.

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

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

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

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

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

Псевдокод

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

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

// Общий интерфейс посредников.
interface Mediator is
    method notify(type: string, sender: Component)


// Конкретный посредник. Все связи между конкретными компонентами переехали в
// код посредника. Он получает извещения от своих компонентов и знает как на
// них реагировать.
class AuthenticationDialog implements Mediator is
    field title: string
    field loginOrRegister: Checkbox
    field loginUsername, loginPassword: Textbox
    field registrationUsername, registrationPassword, registrationEmail: Textbox
    field ok, cancel: Button

    constructor AuthenticationDialog() is
        Create all component objects.
        Pass "this" to their constructor to register itself as a mediator.

    method notify(type, sender) is
        if (type == "check" and sender == loginOrRegister)
            Show login fields, hide registration fields or vise versa.
            if (loginOrRegister.checked)
                title = "Log in"
            else
                title = "Register"
        if (type == "click" and sender == ok)
            if (loginOrRegister.checked)
                Try to find user using login credentials.
                if (!found)
                    Show errors over login fields.
            else
                Create account using registration fields.
                Log user in.
        // ...


// Классы компонентов общаются с посредниками через их общий интерфейс.
// Благодаря этому, одни и те же компоненты можно использовать в
// разных посредниках.
class Component is
    field parent: Mediator

    constructor Component(parent) is
        this.parent = parent

    method click() is
        parent.notify("click", this)

    method keypress() is
        parent.notify("keypress", this)

// Конкретные компоненты никак не связаны между собой. У них есть только один
// канал общения – через отправку уведомлений посреднику.
class Button extends Component is
    // ...

class Textbox extends Component is
    // ...

class Checkbox extends Component is
    method check() is
        parent.notify("check", this)
    // ...

Применимость

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

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

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

После применения паттерна, компоненты теряют прежние связи с другими компонентами. А всё их общение происходит косвенно, через посредника.

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

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

Шаги реализации

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

  2. Создайте общий интерфейс Посредников и опишите в нём методы для взаимодействия с Компонентами. В простейшем случае достаточно одного метода для получения оповещений от компонентов.

  3. Реализуйте этот интерфейс в классе Конкретного посредника. Поместите в него поля, которые будут содержать ссылки на все объекты компонентов.

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

  5. Компоненты тоже должны иметь ссылку на объект посредника. Связь между ними удобней всего установить, подавая посредника в параметры конструктора компонентов.

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

Преимущества и недостатки

  • Устраняет зависимости между компонентами.
  • Упрощает взаимодействие между компонентами.
  • Централизует управление в одном месте.

Отношения с другими паттернами

  • Цепочка обязанностей, Команда, Посредник и Наблюдатель показывают различные способы работы отправителей запросов с их получателями:

    • Цепочка обязанностей передаёт запрос последовательно через цепочку потенциальных получателей, ожидая, что какой-то из них обработает запрос.

    • Наблюдатель передаёт запрос одновременно всем заинтересованным получателям, но позволяет им динамически подписывать или отписываться от таких оповещений.

    • Команда устанавливает косвенную одностороннюю связь от отправителей к получателям.

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

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

  • Разница между Посредником и Наблюдателем не всегда очевидна. Чаще всего они выступают как конкуренты, но иногда могут работать вместе.

    Цель Посредника — убрать обоюдные зависимости между компонентами системы. Вместо этого они становятся зависимыми от самого посредника. С другой стороны, цель Наблюдателя — обеспечить динамическую одностороннюю связь, в которой одни объекты косвенно зависят от других.

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

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

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

Реализация в различных языках программирования

Java