Абстрактна фабрика
Суть патерна
Абстрактна фабрика — це породжувальний патерн проектування, що дає змогу створювати сімейства пов’язаних об’єктів, не прив’язуючись до конкретних класів створюваних об’єктів.
Проблема
Уявіть, що ви пишете симулятор меблевого магазину. Ваш код містить:
-
Сімейство залежних продуктів. Скажімо,
Крісло
+Диван
+Столик
. -
Кілька варіацій цього сімейства. Наприклад, продукти
Крісло
,Диван
таСтолик
представлені в трьох різних стилях:Ар-деко
,Вікторіанському
іМодерн
.
Вам потрібно створювати об’єкти продуктів у такий спосіб, щоб вони завжди пасували до інших продуктів того самого сімейства. Це дуже важливо, адже клієнти засмучуються, коли отримують меблі, що не можна поєднати між собою.
Крім того, ви не хочете вносити зміни в існуючий код під час додавання в програму нових продуктів або сімейств. Постачальники часто оновлюють свої каталоги, але ви б не хотіли змінювати вже написаний код кожен раз при надходженні нових моделей меблів.
Рішення
Для початку, патерн Абстрактна фабрика пропонує виділити загальні інтерфейси для окремих продуктів, що складають одне сімейство, і описати в них спільну для цих продуктів поведінку. Так, наприклад, усі варіації крісел отримають спільний інтерфейс Крісло
, усі дивани реалізують інтерфейс Диван
тощо.
Далі ви створюєте абстрактну фабрику — загальний інтерфейс, який містить методи створення всіх продуктів сімейства (наприклад, створитиКрісло
, створитиДиван
і створитиСтолик
). Ці операції повинні повертати абстрактні типи продуктів, представлені інтерфейсами, які ми виділили раніше — Крісла
, Дивани
і Столики
.
Як щодо варіацій продуктів? Для кожної варіації сімейства продуктів ми повинні створити свою власну фабрику, реалізувавши абстрактний інтерфейс. Фабрики створюють продукти однієї варіації. Наприклад, ФабрикаМодерн
буде повертати тільки КріслаМодерн
,ДиваниМодерн
і СтоликиМодерн
.
Клієнтський код повинен працювати як із фабриками, так і з продуктами тільки через їхні загальні інтерфейси. Це дозволить подавати у ваші класи будь-які типи фабрик і виробляти будь-які типи продуктів, без необхідності вносити зміни в існуючий код.
Наприклад, клієнтський код просить фабрику зробити стілець. Він не знає, якому типу відповідає ця фабрика. Він не знає, отримає вікторіанський або модерновий стілець. Для нього важливо, щоб на цьому стільці можна було сидіти та щоб цей стілець відмінно виглядав поруч із диваном тієї ж фабрики.
Залишилося прояснити останній момент: хто ж створює об’єкти конкретних фабрик, якщо клієнтський код працює лише із загальними інтерфейсами? Зазвичай програма створює конкретний об’єкт фабрики під час запуску, причому тип фабрики вибирається на підставі параметрів оточення або конфігурації.
Структура
-
Абстрактні продукти оголошують інтерфейси продуктів, що пов’язані один з одним за змістом, але виконують різні функції.
-
Конкретні продукти — великий набір класів, що належать до різних абстрактних продуктів (крісло/столик), але мають одні й ті самі варіації (Вікторіанський/Модерн).
-
Абстрактна фабрика оголошує методи створення різних абстрактних продуктів (крісло/столик).
-
Конкретні фабрики кожна належить до своєї варіації продуктів (Вікторіанський/Модерн) і реалізує методи абстрактної фабрики, даючи змогу створювати всі продукти певної варіації.
-
Незважаючи на те, що конкретні фабрики породжують конкретні продукти, сигнатури їхніх методів мусять повертати відповідні абстрактні продукти. Це дозволить клієнтського коду, що використовує фабрику, не прив’язуватися до конкретних класів продуктів. Клієнт зможе працювати з будь-якими варіаціями продуктів через абстрактні інтерфейси.
Псевдокод
У цьому прикладі Абстрактна фабрика створює крос-платформові елементи інтерфейсу і стежить за тим, щоб вони відповідали обраній операційній системі.
Крос-платформова програма може відображати одні й ті самі елементи інтерфейсу по-різному, в залежності від обраної операційної системи. Важливо, щоб у такій програмі всі створювані елементи завжди відповідали поточній операційній системі. Ви ж не хотіли б, аби програма, запущена на Windows, раптом почала показувати чек-бокси в стилі macOS?
Абстрактна фабрика оголошує список створюючих методів, які клієнтський код може використовувати для отримання тих чи інших різновидів елементів інтерфейсу. Конкретні фабрики відносяться до різних операційних систем і створюють елементи, сумісні з цією системою.
Програма на самому початку визначає фабрику, що відповідає поточній операційній системі. Потім створює цю фабрику та віддає її клієнтському коду. У подальшому, щоб виключити несумісність продуктів, що повертаються, клієнт працюватиме тільки з цією фабрикою.
Клієнтський код не залежить від конкретних класів фабрик чи елементів інтерфейсу. Він спілкується з ними через загальні інтерфейси, не залежачи від конкретних класів фабрик чи елементів користувацького інтерфейсу.
Таким чином, щоб додати до програми нову варіацію елементів інтерфейсу (наприклад, для підтримки Linux), вам не потрібно змінювати клієнтський код. Достатньо створити ще одну фабрику, що виготовляє ці елементи.
Застосування
Коли бізнес-логіка програми повинна працювати з різними видами пов’язаних один з одним продуктів, незалежно від конкретних класів продуктів.
Абстрактна фабрика приховує від клієнтського коду подробиці того, як і які конкретно об’єкти будуть створені. Внаслідок цього, клієнтський код може працювати з усіма типами створюваних продуктів, так як їхній загальний інтерфейс був визначений заздалегідь.
Коли в програмі вже використовується Фабричний метод, але чергові зміни передбачають введення нових типів продуктів.
У будь-якій добротній програмі кожен клас має відповідати лише за одну річ. Якщо клас має занадто багато фабричних методів, вони здатні затуманити його основну функцію. Тому є сенс у тому, щоб винести усю логіку створення продуктів в окрему ієрархію класів, застосувавши абстрактну фабрику.
Кроки реалізації
-
Створіть таблицю співвідношень типів продуктів до варіацій сімейств продуктів.
-
Зведіть усі варіації продуктів до загальних інтерфейсів.
-
Визначте інтерфейс абстрактної фабрики. Він повинен мати фабричні методи для створення кожного типу продуктів.
-
Створіть класи конкретних фабрик, реалізувавши інтерфейс абстрактної фабрики. Цих класів має бути стільки ж, скільки й варіацій сімейств продуктів.
-
Змініть код ініціалізації програми так, щоб вона створювала певну фабрику й передавала її до клієнтського коду.
-
Замініть у клієнтському коді ділянки створення продуктів через конструктор на виклики відповідних методів фабрики.
Переваги та недоліки
- Гарантує поєднання створюваних продуктів.
- Звільняє клієнтський код від прив’язки до конкретних класів продукту.
- Виділяє код виробництва продуктів в одне місце, спрощуючи підтримку коду.
- Спрощує додавання нових продуктів до програми.
- Реалізує принцип відкритості/закритості.
- Ускладнює код програми внаслідок введення великої кількості додаткових класів.
- Вимагає наявності всіх типів продукту в кожній варіації.
Відносини з іншими патернами
-
Багато архітектур починаються із застосування Фабричного методу (простішого та більш розширюваного за допомогою підкласів) та еволюціонують у бік Абстрактної фабрики, Прототипу або Будівельника (гнучкіших, але й складніших).
-
Будівельник концентрується на будівництві складних об’єктів крок за кроком. Абстрактна фабрика спеціалізується на створенні сімейств пов’язаних продуктів. Будівельник повертає продукт тільки після виконання всіх кроків, а Абстрактна фабрика повертає продукт одразу.
-
Класи Абстрактної фабрики найчастіше реалізуються за допомогою Фабричного методу, хоча вони можуть бути побудовані і на основі Прототипу.
-
Абстрактна фабрика може бути використана замість Фасаду для того, щоб приховати платформо-залежні класи.
-
Абстрактна фабрика може працювати спільно з Мостом. Це особливо корисно, якщо у вас є абстракції, які можуть працювати тільки з деякими реалізаціями. В цьому випадку фабрика визначатиме типи створюваних абстракцій та реалізацій.
-
Абстрактна фабрика, Будівельник та Прототип можуть реалізовуватися за допомогою Одинака.
Додаткові матеріали
- Якщо ви вже чули про Фабрику, Фабричний метод чи Абстрактну фабрику, але вам все одно важко їх розрізняти, прочитайте нашу статтю Порівняння фабрик.