Также известен как 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. Измените код компонентов так, чтобы они вызывали метод оповещения посредника, а не методы других компонентов. С другого конца, посредник должен вызывать методы нужного компонента, когда получает оповещение.

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

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

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

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

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

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

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

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

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

Java