У чому різниця між Factory Method та Abstract Factory
Обидва патерни належать до категорії породжувальних (Creational) і вирішують завдання відділення процесу створення об'єктів від їхнього безпосереднього використання клієнтським...
🟢 Junior Level
Обидва патерни належать до категорії породжувальних (Creational) і вирішують завдання відділення процесу створення об’єктів від їхнього безпосереднього використання клієнтським кодом. Головна різниця полягає в масштабі та кількості створюваних об’єктів:
- Factory Method (Фабричний метод): створює один конкретний продукт за допомогою поліморфізму та наслідування.
- Abstract Factory (Абстрактна фабрика): створює ціле сімейство взаємопов’язаних продуктів за допомогою композиції.
1. Factory Method (Фабричний метод)
Базовий абстрактний клас делегує створення одного конкретного об’єкта своїм підкласам:
// Продукт
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String message) { System.out.println("Email: " + message); }
}
class SmsNotification implements Notification {
public void send(String message) { System.out.println("SMS: " + message); }
}
// Творець із фабричним методом
abstract class NotificationService {
// Бізнес-логіка спирається на створений продукт (Template Method)
public void notifyUser(String message) {
Notification notification = createNotification(); // Виклик Factory Method
notification.send(message);
}
// Фабричний метод (перевизначається в підкласах)
protected abstract Notification createNotification();
}
class EmailNotificationService extends NotificationService {
@Override
protected Notification createNotification() {
return new EmailNotification();
}
}
2. Abstract Factory (Абстрактна фабрика)
Інтерфейс фабрики оголошує методи для створення групи взаємопов’язаних об’єктів (сімейства), гарантуючи їхню повну сумісність:
// Сімейство продуктів: Кнопка та Чекбокс
interface Button { void paint(); }
interface Checkbox { void paint(); }
// Windows-реалізації
class WindowsButton implements Button { public void paint() { System.out.println("Win Button"); } }
class WindowsCheckbox implements Checkbox { public void paint() { System.out.println("Win Checkbox"); } }
// Mac-реалізації
class MacButton implements Button { public void paint() { System.out.println("Mac Button"); } }
class MacCheckbox implements Checkbox { public void paint() { System.out.println("Mac Checkbox"); } }
// Абстрактна фабрика сімейства компонентів графічного інтерфейсу
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WindowsFactory implements GUIFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
🟡 Middle Level
Механізм реалізації: Наслідування проти Композиції
| Характеристика | Factory Method | Abstract Factory |
|---|---|---|
| Основний механізм | Наслідування (Inheritance): створення об’єкта відбувається в підкласі через перевизначення методу. | Композиція (Composition): екземпляр фабрики передається клієнту як залежність (DI). |
| Що створює | Один поліморфний продукт. | Ціле сімейство узгоджених продуктів. |
| Точка розширення | Додавання нового класу-творця та нового продукту. | Створення нового класу фабрики для нового сімейства. |
| Зв’язаність із клієнтом | Клієнт часто сам є базовим класом (Template Method). | Клієнт повністю ізольований від фабрики і взаємодіє з нею через інтерфейс. |
Приклад використання Abstract Factory клієнтським кодом
Клієнтський код повністю абстрагований від конкретних реалізацій платформи:
public class Application {
private final Button button;
private final Checkbox checkbox;
// Фабрика впроваджується через конструктор (композиція / Dependency Injection)
public Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void render() {
button.paint();
checkbox.paint();
}
}
Гарантія узгодженості: неможливо випадково об’єднати WindowsButton та MacCheckbox, оскільки конкретна фабрика виготовляє виключно елементи одного спільного стилю.
Відмінності: Simple Factory vs Factory Method vs Abstract Factory
1. Simple Factory (статична фабрика, не патерн GoF):
Звичайний клас зі статичним методом та switch/if:
NotificationFactory.create("SMS") -> повертає об'єкт.
❌ Порушує Open/Closed Principle (вимагає модифікації методу при додаванні нового типу).
2. Factory Method (GoF):
Поліморфний віртуальний метод в абстрактному класі.
Базовий клас не знає точного типу продукту; тип визначається підкласом через наслідування.
3. Abstract Factory (GoF):
Інтерфейс фабрики з кількома фабричними методами.
Об'єднує створення узгоджених сімейств продуктів в один контракт за допомогою композиції.
🔴 Senior Level
Дилема Open/Closed Principle (OCP) в Abstract Factory
Патерн Abstract Factory демонструє цікавий архітектурний дуалізм щодо принципу відкритості/закритості (OCP):
- Легко додавати нові сімейства продуктів (✅ OCP дотримується):
Щоб додати тему оформлення Linux, достатньо створити
LinuxFactory, що реалізуєGUIFactory, та класиLinuxButton,LinuxCheckbox. Наявний код клієнтів не зазнає жодних змін. - Вкрай важко додавати нові типи продуктів (❌ OCP грубо порушується):
Якщо в інтерфейс потрібно додати новий елемент, наприклад,
createTextField():- Доведеться змінити контракт інтерфейсу
GUIFactory. - Усі існуючі реалізації фабрик (
WindowsFactory,MacFactory,LinuxFactory) втратять здатність компілюватися, доки в кожній із них не буде реалізовано новий метод.
- Доведеться змінити контракт інтерфейсу
Вплив на JIT-компіляцію: Поліморфізм викликів (Monomorphic vs Megamorphic)
На рівні віртуальної машини JVM спосіб використання фабрики безпосередньо впливає на оптимізації JIT-компілятора (C2 Compiler):
- Monomorphic Call (1 конкретна фабрика в точці виклику в runtime):
JIT виконує спекулятивний інлайнінг (Inlining). Виклик
factory.createButton()замінюється безпосередньо на машинну інструкцію виділення пам’ятіnew WindowsButton(), повністю нівелюючи накладні витрати на віртуальний виклик. - Bimorphic Call (2 типи фабрик):
Компілятор генерує швидку перевірку типу (
if (class == WindowsFactory)), зберігаючи майже максимальну швидкість. - Megamorphic Call (3 і більше типів фабрик в одній точці виклику): Інлайнінг вимикається. Виклик методу виконується через таблицю віртуальних методів (vtable dispatch), що призводить до непрямої адресації та скидання конвеєра інструкцій процесора.
Modern Java: Заміна Factory Method на Supplier<T> та посилання на конструктори
У сучасній Java (Java 8+) важкі ієрархії класів із Factory Method найчастіше замінюються функціональними інтерфейсами та method references:
import java.util.function.Supplier;
public class NotificationManager {
private final Supplier<Notification> notificationSupplier;
// Передаємо посилання на конструктор замість створення ієрархії підкласів
public NotificationManager(Supplier<Notification> notificationSupplier) {
this.notificationSupplier = notificationSupplier;
}
public void process(String msg) {
Notification notification = notificationSupplier.get();
notification.send(msg);
}
}
// Використання:
NotificationManager emailManager = new NotificationManager(EmailNotification::new);
NotificationManager smsManager = new NotificationManager(SmsNotification::new);
А сімейство продуктів для Abstract Factory лаконічно конфігурується за допомогою Java Records:
import java.util.function.Supplier;
public record GuiComponents(
Supplier<Button> buttonSupplier,
Supplier<Checkbox> checkboxSupplier
) {}
// Конфігурація сімейств UI:
public class GuiKits {
public static final GuiComponents WIN_KIT = new GuiComponents(WindowsButton::new, WindowsCheckbox::new);
public static final GuiComponents MAC_KIT = new GuiComponents(MacButton::new, MacCheckbox::new);
}
Spring Framework як глобальна Meta-Factory
У корпоративних enterprise-застосунках потреба в ручній реалізації Abstract Factory практично відсутня:
- Контейнери
BeanFactoryтаApplicationContextвиступають у ролі універсальної мета-фабрики компонентів. - Вибір сімейств залежностей здійснюється декларативно за допомогою профілів (
@Profile("dev")vs@Profile("prod")) або умовних анотацій (@ConditionalOnProperty).
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class StorageConfig {
@Bean
@Profile("cloud")
public StorageService s3Storage() {
return new S3StorageService();
}
@Bean
@Profile("local")
public StorageService localStorage() {
return new LocalFileSystemStorageService();
}
}
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь (Elevator Pitch)
«Ключова різниця між Factory Method та Abstract Factory полягає в масштабі та механізмі реалізації. Factory Method спирається на наслідування і делегує створення одного конкретного продукту підкласам творця. Abstract Factory спирається на композицію і є інтерфейсом для створення цілого сімейства взаємопов’язаних продуктів, гарантуючи їхню повну сумісність (наприклад, комплект компонентів UI для конкретної ОС). В Abstract Factory додавання нового сімейства відбувається елегантно без зміни наявного коду, але додавання нового типу продукту ламає інтерфейс фабрики та всі його конкретні реалізації».
3–4 каверзних запитання з відповідями
- Чому додавання нового типу продукту в Abstract Factory порушує принцип Open/Closed (OCP)?
- Відповідь: Інтерфейс абстрактної фабрики жорстко фіксує набір методів конструювання (
createButton(),createCheckbox()). Додавання нового методуcreateSlider()змушує змінювати сам інтерфейс фабрики, що ламає компіляцію в усіх без винятку наявних класах конкретних фабрик.
- Відповідь: Інтерфейс абстрактної фабрики жорстко фіксує набір методів конструювання (
- Як реалізувати патерн Factory Method у сучасній Java без побудови громіздкої ієрархії підкласів?
- Відповідь: За допомогою функціонального інтерфейсу
java.util.function.Supplier<T>та посилань на конструкториClass::new. Замість наслідування сервіс приймаєSupplier<Product>у конструкторі, забезпечуючи поліморфне створення об’єктів без розростання класів.
- Відповідь: За допомогою функціонального інтерфейсу
- Чи пов’язані між собою Factory Method та Abstract Factory всередині однієї кодової бази?
- Відповідь: Так. На практиці конкретні методи інтерфейсу Abstract Factory (наприклад,
createButton(),createCheckbox()) у конкретних реалізаціях фабрик (WindowsFactory,MacFactory) найчастіше реалізуються саме як фабричні методи (Factory Method).
- Відповідь: Так. На практиці конкретні методи інтерфейсу Abstract Factory (наприклад,
- Як Spring Framework реалізує концепцію Abstract Factory?
- Відповідь: Сам
ApplicationContextє реалізацією фабрики бінів. Механізм конфігурації@Beanу поєднанні з аннотаціями@Profileта@Conditionalдозволяє перемикати цілі взаємопов’язані сімейства залежностей (наприклад, робота з Kafka/PostgreSQL у хмарі та H2/Mock у тестах) без зміни прикладного бізнес-коду.
- Відповідь: Сам
Типові помилки та Red Flags
- 🚩 Використання Abstract Factory для створення лише одного продукту — явний приклад overengineering, що непотрібно ускладнює архітектуру замість простого Factory Method або
Supplier. - 🚩 Змішування понять Simple Factory та Factory Method — плутанина між звичайним статичним методом із оператором
switchі справжнім поліморфним патерном GoF. - 🚩 Ігнорування ризику порушення OCP за умов часто змінної номенклатури продуктів в Abstract Factory.
- 🚩 Створення жорстких ручних фабричних ієрархій у Spring-застосунках замість використання вбудованого механізму впровадження залежностей та конфігураційних класів.
Пов’язані теми
- Коли використовувати Builder — альтернатива для покрокового конструювання складних об’єктів
- Що таке Prototype pattern — створення нових об’єктів шляхом клонування прототипу
- Що таке патерни проектування — огляд класифікації GoF
- Які категорії патернів існують — породжувальні, структурні та поведінкові шаблони
- Застосування SOLID принципів — фундамент чистої архітектури ПЗ