Посередник
Суть патерна
Посередник — це поведінковий патерн проектування, що дає змогу зменшити зв’язаність великої кількості класів між собою, завдяки переміщенню цих зв’язків до одного класу-посередника.
Проблема
Припустімо, що у вас є діалог створення профілю користувача. Він складається з різноманітних елементів керування: текстових полів, чекбоксів, кнопок.
Окремі елементи діалогу повинні взаємодіяти одне з одним. Так, наприклад, чекбокс «у мене є собака» відкриває приховане поле для введення імені домашнього улюбленця, а клік по кнопці збереження запускає перевірку значень усіх полів форми.
Прописавши цю логіку безпосередньо в коді елементів керування, ви поставите хрест на їхньому повторному використанні в інших місцях програми. Вони стануть занадто тісно пов’язаними з елементами діалогу редагування профілю, які не потрібні в інших контекстах. Отже ви зможете або використовувати всі елементи відразу, або не використовувати жоден.
Рішення
Патерн Посередник змушує об’єкти спілкуватися через окремий об’єкт-посередник, який знає, кому потрібно перенаправити той або інший запит. Завдяки цьому компоненти системи залежатимуть тільки від посередника, а не від десятків інших компонентів.
У нашому прикладі посередником міг би стати діалог. Імовірно, клас діалогу вже знає, з яких елементів він складається. Тому жодних нових зв’язків додавати до нього не доведеться.
Основні зміни відбудуться всередині окремих елементів діалогу. Якщо раніше при отриманні кліка від користувача об’єкт кнопки самостійно перевіряв значення полів діалогу, то тепер його єдиний обов’язок — повідомити діалогу про те, що відбувся клік. Отримавши повідомлення, діалог виконає всі необхідні перевірки полів. Таким чином, замість кількох залежностей від інших елементів кнопка отримає лише одну — від самого діалогу.
Щоб зробити код ще гнучкішим, можна виділити єдиний інтерфейс для всіх посередників, тобто діалогів програми. Наша кнопка стане залежною не від конкретного діалогу створення користувача, а від абстрактного, що дозволить використовувати її і в інших діалогах.
Таким чином, посередник приховує у собі всі складні зв’язки й залежності між класами окремих компонентів програми. А чим менше зв’язків мають класи, тим простіше їх змінювати, розширювати й повторно використовувати.
Аналогія з життя
Пілоти літаків, що сідають або злітають, не спілкуються з іншими пілотами безпосередньо. Замість цього вони зв’язуються з диспетчером, який координує політ кількох літаків одночасно. Без диспетчера пілотам доводилося б увесь час бути напоготові і стежити самостійно за всіма літаками навколо. Це часто призводило б до катастроф у небі.
Важливо розуміти, що диспетчер не потрібен під час всього польоту. Він задіяний тільки в зоні аеропорту, коли потрібно координувати взаємодію багатьох літаків.
Структура
-
Компоненти — це різнорідні об’єкти, що містять бізнес-логіку програми. Кожен компонент має посилання на об’єкт посередника, але працює з ним тільки через абстрактний інтерфейс посередників. Завдяки цьому компоненти можна повторно використовувати в інших програмах, зв’язавши їх з посередником іншого типу.
-
Посередник визначає інтерфейс для обміну інформацією з компонентами. Зазвичай достатньо одного методу, щоби повідомляти посередника про події, що відбулися в компонентах. У параметрах цього методу можна передавати деталі події: посилання на компонент, в якому вона відбулася, та будь-які інші дані.
-
Конкретний посередник містить код взаємодії кількох компонентів між собою. Найчастіше цей об’єкт не тільки зберігає посилання на всі свої компоненти, але й сам їх створює, керуючи подальшим життєвим циклом.
-
Компоненти не повинні спілкуватися один з одним безпосередньо. Якщо в компоненті відбувається важлива подія, він повинен повідомити свого посередника, а той сам вирішить, чи стосується подія інших компонентів, і чи треба їх сповістити. При цьому компонент-відправник не знає, хто обробить його запит, а компонент-одержувач не знає, хто його надіслав.
Псевдокод
У цьому прикладі Посередник допомагає позбутися залежностей між класами різних елементів користувацього інтерфейсу: кнопками, чекбоксами й написами.
Реагуючи на дії користувачів, елементи не взаємодіють безпосередньо, а лише повідомляють посередника про те, що вони змінилися.
Посередник у вигляді діалогу авторизації знає, як конкретні елементи повинні взаємодіяти. Тому при отриманні повідомлень він може перенаправити виклик тому чи іншому елементу.
Придатність
-
Коли вам складно змінювати деякі класи через те, що вони мають величезну кількість хаотичних зв’язків з іншими класами.
-
Посередник дозволяє розмістити усі ці зв’язки в одному класі. Після цього вам буде легше їх відрефакторити, зробити більш зрозумілими й гнучкими.
-
Коли ви не можете повторно використовувати клас, оскільки він залежить від безлічі інших класів.
-
Після застосування патерна компоненти втрачають колишні зв’язки з іншими компонентами, а все їхнє спілкування відбувається опосередковано, через об’єкт посередника.
-
Коли вам доводиться створювати багато підкласів компонентів, щоб використовувати одні й ті самі компоненти в різних контекстах.
-
Якщо раніше зміна відносин в одному компоненті могла призвести до лавини змін в усіх інших компонентах, то тепер вам достатньо створити підклас посередника та змінити в ньому зв’язки між компонентами.
Кроки реалізації
-
Знайдіть групу тісно сплетених класів, де можна отримати деяку користь, відв’язавши деякі один від одного. Наприклад, щоб повторно використовувати їхній код в іншій програмі.
-
Створіть загальний інтерфейс посередників та опишіть в ньому методи для взаємодії з компонентами. У найпростішому випадку достатньо одного методу для отримання повідомлень від компонентів.
Цей інтерфейс необхідний, якщо ви хочете повторно використовувати класи компонентів для інших завдань. У цьому випадку все, що потрібно зробити, — це створити новий клас конкретного посередника.
-
Реалізуйте цей інтерфейс у класі конкретного посередника. Помістіть до нього поля, які міститимуть посилання на всі об’єкти компонентів.
-
Ви можете піти далі і перемістити код створення компонентів до класу конкретного посередника, перетворивши його на фабрику.
-
Компоненти теж повинні мати посилання на об’єкт посередника. Зв’язок між ними зручніше всього встановити шляхом подання посередника до параметрів конструктора компонентів.
-
Змініть код компонентів так, щоб вони викликали метод повідомлення посередника, замість методів інших компонентів. З протилежного боку, посередник має викликати методи потрібного компонента, коли отримує повідомлення від компонента.
Переваги та недоліки
- Усуває залежності між компонентами, дозволяючи використовувати їх повторно.
- Спрощує взаємодію між компонентами.
- Централізує керування в одному місці.
- Посередник може сильно «роздутися».
Відносини з іншими патернами
-
Ланцюжок обов’язків, Команда Посередник та Спостерігач показують різні способи роботи тих, хто надсилає запити, та тих, хто їх отримує:
- Ланцюжок обов’язків передає запит послідовно через ланцюжок потенційних отримувачів, очікуючи, що один з них обробить запит.
- Команда встановлює непрямий односторонній зв’язок від відправників до одержувачів.
- Посередник прибирає прямий зв’язок між відправниками та одержувачами, змушуючи їх спілкуватися опосередковано, через себе.
- Спостерігач передає запит одночасно всім зацікавленим одержувачам, але дозволяє їм динамічно підписуватися або відписуватися від таких повідомлень.
-
Посередник та Фасад схожі тим, що намагаються організувати роботу багатьох існуючих класів.
- Фасад створює спрощений інтерфейс підсистеми, не вносячи в неї жодної додаткової функціональності. Сама підсистема не знає про існування Фасаду. Класи підсистеми спілкуються один з одним безпосередньо.
- Посередник централізує спілкування між компонентами системи. Компоненти системи знають тільки про існування Посередника, у них немає прямого доступу до інших компонентів.
-
Різниця між Посередником та Спостерігачем не завжди очевидна. Найчастіше вони виступають як конкуренти, але іноді можуть працювати разом.
Мета Посередника — прибрати взаємні залежності між компонентами системи. Замість цього вони стають залежними від самого посередника. З іншого боку, мета Спостерігача — забезпечити динамічний односторонній зв’язок, в якому одні об’єкти опосередковано залежать від інших.
Досить популярною є реалізація Посередника за допомогою Спостерігача. При цьому об’єкт посередника буде виступати видавцем, а всі інші компоненти стануть передплатниками та зможуть динамічно стежити за подіями, що відбуваються у посереднику. У цьому випадку важко зрозуміти, чим саме відрізняються обидва патерни.
Але Посередник має й інші реалізації, коли окремі компоненти жорстко прив’язані до об’єкта посередника. Такий код навряд чи буде нагадувати Спостерігача, але залишиться Посередником.
Навпаки, у разі реалізації посередника з допомогою Спостерігача, представимо чи уявімо таку програму, в якій кожен компонент системи стає видавцем. Компоненти можуть підписуватися один на одного, не прив’язуючись до конкретних класів. Програма складатиметься з цілої мережі Спостерігачів, не маючи центрального об’єкта Посередника.