🧩 Розділ 2 · Питання #7

У чому різниця між 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):

  1. Легко додавати нові сімейства продуктів (✅ OCP дотримується): Щоб додати тему оформлення Linux, достатньо створити LinuxFactory, що реалізує GUIFactory, та класи LinuxButton, LinuxCheckbox. Наявний код клієнтів не зазнає жодних змін.
  2. Вкрай важко додавати нові типи продуктів (❌ OCP грубо порушується): Якщо в інтерфейс потрібно додати новий елемент, наприклад, createTextField():
    • Доведеться змінити контракт інтерфейсу GUIFactory.
    • Усі існуючі реалізації фабрик (WindowsFactory, MacFactory, LinuxFactory) втратять здатність компілюватися, доки в кожній із них не буде реалізовано новий метод.

Вплив на JIT-компіляцію: Поліморфізм викликів (Monomorphic vs Megamorphic)

На рівні віртуальної машини JVM спосіб використання фабрики безпосередньо впливає на оптимізації JIT-компілятора (C2 Compiler):

  1. Monomorphic Call (1 конкретна фабрика в точці виклику в runtime): JIT виконує спекулятивний інлайнінг (Inlining). Виклик factory.createButton() замінюється безпосередньо на машинну інструкцію виділення пам’яті new WindowsButton(), повністю нівелюючи накладні витрати на віртуальний виклик.
  2. Bimorphic Call (2 типи фабрик): Компілятор генерує швидку перевірку типу (if (class == WindowsFactory)), зберігаючи майже максимальну швидкість.
  3. 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 каверзних запитання з відповідями

  1. Чому додавання нового типу продукту в Abstract Factory порушує принцип Open/Closed (OCP)?
    • Відповідь: Інтерфейс абстрактної фабрики жорстко фіксує набір методів конструювання (createButton(), createCheckbox()). Додавання нового методу createSlider() змушує змінювати сам інтерфейс фабрики, що ламає компіляцію в усіх без винятку наявних класах конкретних фабрик.
  2. Як реалізувати патерн Factory Method у сучасній Java без побудови громіздкої ієрархії підкласів?
    • Відповідь: За допомогою функціонального інтерфейсу java.util.function.Supplier<T> та посилань на конструктори Class::new. Замість наслідування сервіс приймає Supplier<Product> у конструкторі, забезпечуючи поліморфне створення об’єктів без розростання класів.
  3. Чи пов’язані між собою Factory Method та Abstract Factory всередині однієї кодової бази?
    • Відповідь: Так. На практиці конкретні методи інтерфейсу Abstract Factory (наприклад, createButton(), createCheckbox()) у конкретних реалізаціях фабрик (WindowsFactory, MacFactory) найчастіше реалізуються саме як фабричні методи (Factory Method).
  4. Як 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-застосунках замість використання вбудованого механізму впровадження залежностей та конфігураційних класів.

Пов’язані теми