# Посредник (Mediator)

### Суть паттерна <a href="#intent" id="intent"></a>

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

<figure><img src="https://refactoringguru.cn/images/patterns/content/mediator/mediator.png?id=0264bd857a231b6ea2d0c537c092e698" alt="Паттерн Посредник" width="640"><figcaption></figcaption></figure>

### &#x20;Проблема <a href="#problem" id="problem"></a>

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

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/mediator/problem1-en.png?id=86f99055b3e60bb8834dcc7222922bdf" alt="Беспорядочные связи между элементами пользовательского интерфейса" width="600"><figcaption><p>Беспорядочные связи между элементами пользовательского интерфейса.</p></figcaption></figure>

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

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/mediator/problem2.png?id=072c51eee4dd90c0972866440c87c1c3" alt="Код элементов раздут условиями, которые часто меняются" width="360"><figcaption><p>Код элементов нужно трогать при изменении каждого диалога.</p></figcaption></figure>

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

### &#x20;Решение <a href="#solution" id="solution"></a>

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

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

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/mediator/solution1-en.png?id=dd991a5b7830de8d43f82b084e021713" alt="Элементы общаются через посредника" width="600"><figcaption><p>Элементы интерфейса общаются через посредника.</p></figcaption></figure>

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

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

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

### &#x20;Аналогия из жизни <a href="#analogy" id="analogy"></a>

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/mediator/live-example.png?id=aa1de3cb7b63aa623e63578a1e43399a" alt="Пример с диспетчерской башней." width="370"><figcaption><p>Пилоты самолётов общаются не напрямую, а через диспетчера.</p></figcaption></figure>

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

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

### &#x20;Структура <a href="#structure" id="structure"></a>

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/mediator/structure.png?id=1f2accc7820ecfe9665b6d30cbc0bc61" alt="Структура классов паттерна Посредник" width="520"><figcaption></figcaption></figure>

1. **Компоненты** — это разнородные объекты, содержащие бизнес-логику программы. Каждый компонент хранит ссылку на объект посредника, но работает с ним только через абстрактный интерфейс посредников. Благодаря этому, компоненты можно повторно использовать в другой программе, связав их с посредником другого типа.
2. **Посредник** определяет интерфейс для обмена информацией с компонентами. Обычно хватает одного метода, чтобы оповещать посредника о событиях, произошедших в компонентах. В параметрах этого метода можно передавать детали события: ссылку на компонент, в котором оно произошло, и любые другие данные.
3. **Конкретный посредник** содержит код взаимодействия нескольких компонентов между собой. Зачастую этот объект не только хранит ссылки на все свои компоненты, но и сам их создаёт, управляя дальнейшим жизненным циклом.
4. Компоненты не должны общаться друг с другом напрямую. Если в компоненте происходит важное событие, он должен оповестить своего посредника, а тот сам решит — касается ли событие других компонентов, и стоит ли их оповещать. При этом компонент-отправитель не знает кто обработает его запрос, а компонент-получатель не знает кто его прислал.

### Применимость <a href="#applicability" id="applicability"></a>

&#x20;Когда вам сложно менять некоторые классы из-за того, что они имеют множество хаотичных связей с другими классами.

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

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

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

&#x20;Когда вам приходится создавать множество подклассов компонентов, чтобы использовать одни и те же компоненты в разных контекстах.

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

### &#x20;Шаги реализации <a href="#checklist" id="checklist"></a>

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

   Этот интерфейс необходим, если вы хотите повторно использовать классы компонентов для других задач. В этом случае всё, что нужно сделать — это создать новый класс конкретного посредника.
3. Реализуйте этот интерфейс в классе конкретного посредника. Поместите в него поля, которые будут содержать ссылки на все объекты компонентов.
4. Вы можете пойти дальше и переместить код создания компонентов в класс посредника, после чего он может напоминать фабрику или фасад.
5. Компоненты тоже должны иметь ссылку на объект посредника. Связь между ними удобнее всего установить, подавая посредника в параметры конструктора компонентов.
6. Измените код компонентов так, чтобы они вызывали метод оповещения посредника, вместо методов других компонентов. С противоположной стороны, посредник должен вызывать методы нужного компонента, когда получает оповещение от компонента.

### &#x20;Преимущества и недостатки <a href="#pros-cons" id="pros-cons"></a>

* &#x20;Устраняет зависимости между компонентами, позволяя повторно их использовать.
* &#x20;Упрощает взаимодействие между компонентами.
* &#x20;Централизует управление в одном месте.
* &#x20;Посредник может сильно раздуться.

## Пример

<pre class="language-cpp"><code class="lang-cpp"># include &#x3C;iostream>
# include &#x3C;memory>
# include &#x3C;list>
# include &#x3C;vector>

using namespace std;

class Message {};         // Request

class Mediator;

class Colleague
{
private:
        weak_ptr&#x3C;Mediator> mediator;

public:
        virtual ~Colleague() = default;
        
        void setMediator(shared_ptr&#x3C;Mediator> mdr) { mediator = mdr; }
        
        virtual bool send(shared_ptr&#x3C;Message> msg);
        virtual void receive(shared_ptr&#x3C;Message> msg) = 0;
};

class ColleagueLeft : public Colleague
{
public:
        void receive(shared_ptr&#x3C;Message> msg) override { cout &#x3C;&#x3C; "Right - > Left;" &#x3C;&#x3C; endl; }
};

class ColleagueRight : public Colleague
{
public:
        void receive(shared_ptr&#x3C;Message> msg) override { cout &#x3C;&#x3C; "Left - > Right;" &#x3C;&#x3C; endl; }
};

class Mediator
{
protected:
        list&#x3C;shared_ptr&#x3C;Colleague>> colleagues;

public:
        virtual ~Mediator() = default;
        
        virtual bool send(const Colleague* coleague, shared_ptr&#x3C;Message> msg) = 0;
        
        static bool add(shared_ptr&#x3C;Mediator> mediator, initializer_list&#x3C;shared_ptr&#x3C;Colleague>> list);
};

class ConMediator : public Mediator
{
public:
        bool send(const Colleague* coleague, shared_ptr&#x3C;Message> msg) override;
};

# pragma region Methods Colleague
bool Colleague::send(shared_ptr&#x3C;Message> msg)
{
        shared_ptr&#x3C;Mediator> mdr = mediator.lock();

        return mdr ? mdr->send(this, msg) : false;
}
# pragma endregion

# pragma region Methods Mediator
bool Mediator::add(shared_ptr&#x3C;Mediator> mediator, initializer_list&#x3C;shared_ptr&#x3C;Colleague>> list)
{
        if (!mediator || list.size() == 0) return false;

        for (auto elem : list)
        {
                mediator->colleagues.push_back(elem);
                elem->setMediator(mediator);
        }

        return true;
}

bool ConMediator::send(const Colleague* colleague, shared_ptr&#x3C;Message> msg)
{
        bool flag = false;
        for (auto&#x26;&#x26; elem : colleagues)
        {
                if (dynamic_cast&#x3C;const ColleagueLeft*>(colleague) &#x26;&#x26; dynamic_cast&#x3C;ColleagueRight*>(elem.get()))
                {
                        elem->receive(msg);
                        flag = true;
                }
<strong>                else if (dynamic_cast&#x3C;const ColleagueRight*>(colleague) &#x26;&#x26; dynamic_cast&#x3C;ColleagueLeft*>(elem.get()))
</strong>                {
                        elem->receive(msg);
                        flag = true;
                }
        }

        return flag;
}
#pragma endregion

int main()
{
        shared_ptr&#x3C;Mediator> mediator = make_shared&#x3C;ConMediator>();
        
        shared_ptr&#x3C;Colleague> col1 = make_shared&#x3C;ColleagueLeft>();
        shared_ptr&#x3C;Colleague> col2 = make_shared&#x3C;ColleagueRight>();
        shared_ptr&#x3C;Colleague> col3 = make_shared&#x3C;ColleagueLeft>();
        shared_ptr&#x3C;Colleague> col4 = make_shared&#x3C;ColleagueLeft>();
        
        Mediator::add(mediator, { col1, col2, col3, col4 });
        
        shared_ptr&#x3C;Message> msg = make_shared&#x3C;Message>();
        
        col1->send(msg);
        col2->send(msg);
}

</code></pre>
