Мета
Абстрактна фабрика — це шаблон проектування, що дозволяє створювати родини пов’язаних об'єктів без вказівки їх конкретних класів. Уявімо, що ви створюєте симулятор меблевого магазину, і ваш код складається з класів, які представляють:
- Родину пов’язаних продуктів, таких як стільці, дивани і столи.
- Є кілька варіантів кожної родини, наприклад, сучасний, вікторіанський та артдеко.
Тепер вам потрібен спосіб створювати окремі меблеві об'єкти, щоб вони відповідали іншим об'єктам тієї ж родини, адже клієнти розлючуються, коли отримують меблі, які не відповідають за стилем.
Крім того, ми не хочемо змінювати існуючий код при додаванні нових продуктів або родин продуктів у програму.
Рішення
Рішенням буде використання шаблону абстрактної фабрики.
- Спочатку ми оголосимо інтерфейси для кожного окремого продукту родини продуктів, наприклад, стілець, диван або стіл, і всі варіанти продуктів будуть слідувати цим інтерфейсам. Наприклад: всі варіанти стільців можуть реалізувати інтерфейс
Chair
, всі варіанти столів можуть реалізувати інтерфейсTable
і так далі. - Далі ми створимо абстрактний інтерфейс фабрики з набором методів для створення всіх продуктів, що входять до родини продуктів, наприклад: createChair, createSofa та createTable.
- Ці методи повинні повертати абстрактні типи продуктів, представлені раніше витягнутими інтерфейсами
Chair
,Sofa
,Table
і так далі для їх відповідних методів створення. - Тепер, щоб підтримувати варіанти продуктів, для кожного варіанту родини продуктів ми створимо окремий клас фабрики, заснований на інтерфейсі
AbstractFactory
. - Клас фабрики повертатиме лише продукти цієї родини, тобто певний тип, наприклад, фабрика
ModernFurnitureFactory
може створювати лише сучасні стільці, дивани та столи. - Клієнт повинен обробляти кожен стілець однаково, використовуючи абстракцію
Chair
, не розрізняючи між сучасною чи старою моделлю. Для клієнта важливим буде лише метод sitOn, а не реалізація чи тип стільця, який ми використовуємо для виклику методу onSit. - Інша важлива деталь — незалежно від того, який варіант стільця повертається, інші продукти, такі як стіл або диван, завжди будуть відповідати типу цього варіанту.
- Зазвичай вибір варіанту родини визначається за допомогою налаштувань середовища або конфігурації, залежно від потреб.
Структура:
- Абстрактні продукти — ми оголошуємо інтерфейси для набору окремих, але пов’язаних продуктів, які складають родину продуктів, наприклад: стілець, стіл, диван, що створює меблеву родину.
- Конкретні продукти — це різні реалізації абстрактних продуктів, згруповані за варіантами. Кожен абстрактний продукт повинен бути реалізований у всіх наданих варіантах (сучасний/старий), наприклад:
ModernChair
використовує інтерфейсChair
, подібно доModernSofa
іOldTable
тощо. - Потім ми оголошуємо інтерфейс
AbstractFactory
, який містить набір методів для створення кожного з абстрактних продуктів. - Потім ми маємо
ConcreteFactories
, які реалізують методи створення з абстрактної фабрики. Кожна конкретна фабрика відповідає за конкретний варіант продуктів і створює лише ці варіанти продуктів.
Псевдокод
- Клієнт встановлює поточний варіант, чи то Mac, чи Win, на основі налаштувань застосунку або конфігурації, зазвичай під час ініціалізації або виконання.
- Інтерфейс абстрактної фабрики, яким є інтерфейс
GUIFactory
, повинен оголосити методи, які будуть використовуватися, наприклад:createButton
таcreateCheckbox
, і так далі.
3.
Далі буде конкретний клас фабрики для кожного з варіантів: WinFactory та MacFactory. Ці класи матимуть конкретну реалізацію, необхідну для відповідного варіанту, щоб виконати реальну операцію. Усередині конкретних класів ми повинні реалізувати методи для кожної родини продуктів цього варіанту, наприклад, для кнопок — createButtons, для чекбоксів — createCheckbox. - Тепер методи конкретної фабрики матимуть типи повернених абстрактних продуктів, таких як Button і Checkbox, і ті, що клієнти будуть використовувати — конкретні продукти WinButton, WinCheckbox, MacButton, MacCheckbox — будуть слідувати лише типам абстрактних продуктів, і тому клієнт оброблятиме їх однаково, не розрізняючи їх варіанти.
// Інтерфейс абстрактної фабрики оголошує набір методів, які
// повертають різні абстрактні продукти. Ці продукти називаються
// родиною і пов’язані високорівневою темою або концепцією.
// Продукти однієї родини зазвичай можуть співпрацювати між
// собою. Родина продуктів може мати кілька варіантів,
// але продукти одного варіанту несумісні з
// продуктами іншого варіанту.
interface GUIFactory {
method createButton(): Button
method createCheckbox(): Checkbox
}
// Конкретні фабрики створюють родину продуктів, які належать
// до одного варіанту. Фабрика гарантує, що
// результатуючі продукти сумісні. Підписи методів конкретної
// фабрики повертають абстрактні продукти, але всередині
// методів інстанціюються конкретні продукти.
class WinFactory implements GUIFactory {
method createButton(): Button {
return new WinButton()
}
method createCheckbox(): Checkbox {
return new WinCheckbox()
}
}
// Кожна конкретна фабрика має відповідний варіант продукту.
class MacFactory implements GUIFactory {
method createButton(): Button {
return new MacButton()
}
method createCheckbox(): Checkbox {
return new MacCheckbox()
}
}
// Кожен окремий продукт родини продуктів повинен мати базовий
// інтерфейс. Усі варіанти продукту повинні реалізувати цей
// інтерфейс.
interface Button {
method paint()
}
// Конкретні продукти створюються відповідними конкретними
// фабриками.
class WinButton implements Button {
method paint() {
// Відображення кнопки в стилі Windows.
}
}
class MacButton implements Button {
method paint() {
// Відображення кнопки в стилі macOS.
}
}
// Ось базовий інтерфейс іншого продукту. Усі продукти
// можуть взаємодіяти один з одним, але належна взаємодія
// можлива лише між продуктами одного конкретного варіанту.
interface Checkbox {
method paint()
}
class WinCheckbox implements Checkbox {
method paint() {
// Відображення чекбоксу в стилі Windows.
}
}
class MacCheckbox implements Checkbox {
method paint() {
// Відображення чекбоксу в стилі macOS.
}
}
// Код клієнта працює з фабриками та продуктами тільки
// через абстрактні типи: GUIFactory, Button і Checkbox. Це
// дозволяє передавати будь-яку фабрику або підклас продукту
// у код клієнта без його зламування.
class Application {
private field factory: GUIFactory
private field button: Button
constructor Application(factory: GUIFactory) {
this.factory = factory
}
method createUI() {
this.button = factory.createButton()
}
method paint() {
button.paint()
}
// Застосунок вибирає тип фабрики залежно від поточної
// конфігурації чи налаштувань середовища та створює її
// під час виконання (зазвичай на етапі ініціалізації).
class ApplicationConfigurator {
method main() {
config = readApplicationConfigFile()
if (config.OS == "Windows") {
factory = new WinFactory()
} else if (config.OS == "Mac") {
factory = new MacFactory()
} else {
throw new Exception("Помилка! Невідоме операційне середовище.")
}
Application app = new Application(factory)
}
}
## Використання
Використовуйте абстрактну фабрику, коли вашому коду потрібно працювати з різними родинами взаємопов’язаних продуктів, але ви не хочете, щоб він залежав від конкретних класів цих продуктів — вони можуть бути невідомими заздалегідь, або ви просто хочете дозволити майбутнє розширення. Розгляньте впровадження абстрактної фабрики, коли у вас є клас з набором методів фабрики, які розмивають його основну відповідальність.
## **Як реалізувати**
- Складіть матрицю з різних типів продуктів і варіантів цих продуктів.
- Оголосіть абстрактні інтерфейси продуктів для всіх типів продуктів.
- Тоді змусьте всі конкретні класи продуктів реалізовувати ці інтерфейси.
- Оголосіть інтерфейс абстрактної фабрики з набором методів створення для всіх абстрактних продуктів.
- Реалізуйте набір конкретних класів фабрик, по одному для кожного варіанту продукту.
- Створіть код ініціалізації фабрики де-небудь у додатку.
- Він має інстанціювати один з конкретних класів фабрик, залежно від конфігурації програми або поточного середовища. Передайте цей об'єкт фабрики всім класам, які створюють продукти.
- Перегляньте код і знайдіть всі прямі виклики конструкторів продуктів. Замініть їх на виклики відповідного методу створення на об’єкті фабрики.
## Взаємозв’язки з іншими патернами
- Багато розробок починаються з використання [**Factory Method**](https://refactoring.guru/design-patterns/factory-method) (менш складний і більш налаштовуваний через підкласи) і еволюціонують до [**Abstract Factory**](https://refactoring.guru/design-patterns/abstract-factory), [**Prototype**](https://refactoring.guru/design-patterns/prototype), або [**Builder**](https://refactoring.guru/design-patterns/builder) (більш гнучкий, але складніший).
- [**Builder**](https://refactoring.guru/design-patterns/builder) фокусується на поетапному створенні складних об'єктів. [**Abstract Factory**](https://refactoring.guru/design-patterns/abstract-factory) спеціалізується на створенні родин взаємопов’язаних об'єктів. _Abstract Factory_ повертає продукт безпосередньо, в той час як _Builder_ дозволяє виконати деякі додаткові кроки побудови перед отриманням продукту.
- [**Abstract Factory**](https://refactoring.guru/design-patterns/abstract-factory) класи часто базуються на наборі [**Factory Methods**](https://refactoring.guru/design-patterns/factory-method), але ви також можете використовувати [**Prototype**](https://refactoring.guru/design-patterns/prototype), щоб компонувати методи в цих класах.
- [**Abstract Factory**](https://refactoring.guru/design-patterns/abstract-factory) може бути альтернативою [**Facade**](https://refactoring.guru/design-patterns/facade), коли вам потрібно тільки приховати спосіб створення об'єктів підсистеми від коду клієнта.
- Ви можете використовувати [**Abstract Factory**](https://refactoring.guru/design-patterns/abstract-factory) разом з [**Bridge**](https://refactoring.guru/design-patterns/bridge). Це поєднання корисне, коли деякі абстракції, визначені _Bridge_, можуть працювати тільки з певними реалізаціями. У цьому випадку _Abstract Factory_ може інкапсулювати ці зв’язки та приховати складність від коду клієнта.
- [**Abstract Factories**](https://refactoring.guru/design-patterns/abstract-factory), [**Builders**](https://refactoring.guru/design-patterns/builder) та [**Prototypes**](https://refactoring.guru/design-patterns/prototype) можуть бути реалізовані як [**Singletons**](https://refactoring.guru/design-patterns/singleton).
Дякую за прочитане.
Перекладено з: [Abstract Factory Pattern in 5 Minutes!](https://medium.com/@abhinavpandey0032/abstract-factory-pattern-in-5-minutes-b2cf78b1708e)