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

В чому різниця між Factory Method та Abstract Factory?

Обидва патерни допомагають створювати об'єкти, але на різних рівнях:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Обидва патерни допомагають створювати об’єкти, але на різних рівнях:

Factory Method — створює один тип об’єктів:

// Фабричний метод створює один продукт
interface Button { void render(); }
class WinButton implements Button { ... }
class MacButton implements Button { ... }

abstract class Dialog {
    // Factory Method — підклас вирішує, яку кнопку створити
    abstract Button createButton();
}

Abstract Factory — створює сімейство пов’язаних об’єктів:

// Абстрактна фабрика створює ціле сімейство продуктів
// Пов'язаних = вони мають бути з однієї "сім'ї" (усі Windows-компоненти або усі Mac-компоненти),
// інакше UI виглядатиме неконсистентно.
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
    Menu createMenu();
}

class WinFactory implements GUIFactory {
    Button createButton() { return new WinButton(); }
    Checkbox createCheckbox() { return new WinCheckbox(); }
    Menu createMenu() { return new WinMenu(); }
}

Головна різниця:

  • Factory Method = один продукт (кнопка)
  • Abstract Factory = сімейство продуктів (кнопка + чекбокс + меню)

🟡 Middle Level

OCP (Open/Closed Principle) — відкритий для розширення, закритий для модифікації.

Factory Method (Фабричний метод)

Механізм: Наслідування (підкласи вирішують)

// Abstract class з Factory Method
public abstract class PaymentService {
    // Шаблонний метод
    public void processPayment() {
        PaymentGateway gateway = createGateway();  // Factory Method
        gateway.authorize();
        gateway.capture();
    }

    // Підклас реалізує створення
    abstract PaymentGateway createGateway();
}

// Конкретні реалізації
public class CreditCardService extends PaymentService {
    @Override
    PaymentGateway createGateway() {
        return new StripeGateway();  // Вирішує підклас
    }
}

public class PayPalService extends PaymentService {
    @Override
    PaymentGateway createGateway() {
        return new PayPalGateway();
    }
}

Коли використовувати:

  • Потрібно розширювати систему новими типами
  • Клас не знає, які об’єкти створювати
  • Створення делегується підкласам

Abstract Factory (Абстрактна фабрика)

Механізм: Композиція (об’єкти передаються)

// Інтерфейс фабрики
public interface DatabaseFactory {
    Connection createConnection();
    Statement createStatement();
    ResultSet createResultSet();
}

// Конкретна фабрика для MySQL
public class MySQLFactory implements DatabaseFactory {
    public Connection createConnection() { return new MySQLConnection(); }
    public Statement createStatement() { return new MySQLStatement(); }
    public ResultSet createResultSet() { return new MySQLResultSet(); }
}

// Конкретна фабрика для PostgreSQL
public class PostgresFactory implements DatabaseFactory {
    public Connection createConnection() { return new PostgresConnection(); }
    public Statement createStatement() { return new PostgresStatement(); }
    public ResultSet createResultSet() { return new PostgresResultSet(); }
}

// Клієнт працює з будь-якою фабрикою
public class DatabaseService {
    private final DatabaseFactory factory;

    public DatabaseService(DatabaseFactory factory) {
        this.factory = factory;  // Передаємо потрібну фабрику
    }

    public void query() {
        Connection conn = factory.createConnection();
        Statement stmt = factory.createStatement();
        // Усі об'єкти сумісні!
    }
}

Коли використовувати:

  • Потрібно створити сімейство пов’язаних об’єктів
  • Важливо, щоб об’єкти були з однієї сім’ї
  • Клієнт не повинен знати про конкретні класи

Порівняння

Критерій Factory Method Abstract Factory
Що створює Один об’єкт Сімейство об’єктів
Механізм Наслідування Композиція
Розширення Новий підклас Нова фабрика
Складність Низька Висока
OCP ✅ Дотримується ❌ Порушується при додаванні продукту

Типові помилки

  1. Abstract Factory для одного продукту
    // ❌ Overengineering
    interface ButtonFactory { Button create(); }
    
    // ✅ Достатньо Factory Method
    abstract class Dialog { abstract Button createButton(); }
    
  2. Порушення OCP в Abstract Factory
    // Додали новий продукт → ламаємо ВСІ фабрики
    interface GUIFactory {
        Button createButton();
        Checkbox createCheckbox();
        Tooltip createTooltip();  // ← Нове!
    }
    // Усі реалізації потрібно змінити!
    

🔴 Senior Level

Architectural Scope

Factory Method = точка розширення в ієрархії:

Dialog (абстракція)
  ├── createButton() ← точка розширення
  │     ├── WinButton
  │     └── MacButton
  └── Шаблонний метод використовує createButton()

Abstract Factory = межа сумісності:

GUIFactory (межа сімейства)
  ├── WinFactory → WinButton + WinCheckbox + WinMenu
  └── MacFactory → MacButton + MacCheckbox + MacMenu

Гарантія: продукти з різних сімейств НЕ змішуються!

Open/Closed Principle Trade-offs

Factory Method дотримується OCP:

// Додати новий тип → новий підклас (не змінюємо наявний код)
class CryptoService extends PaymentService {
    @Override
    PaymentGateway createGateway() {
        return new CoinbaseGateway();
    }
}
// ✅ Existing code не змінений

Abstract Factory порушує OCP при розширенні:

// Додати новий продукт → змінити інтерфейс → зламати ВСІ реалізації
interface GUIFactory {
    Button createButton();
    Slider createSlider();  // ← Новий продукт
}

// Усі фабрики потрібно оновити:
class WinFactory implements GUIFactory {
    Slider createSlider() { return new WinSlider(); }  // ← Додати!
}
class MacFactory implements GUIFactory {
    Slider createSlider() { return new MacSlider(); }  // ← Додати!
}
// ❌ Existing code змінений

Еволюція системи

Simple Factory (статичний метод)
  ↓ (потрібна розширюваність)
Factory Method (наслідування)
  ↓ (потрібно кілька продуктів)
Abstract Factory (сімейства)
  ↓ (потрібна гнучкість)
DI Container (Spring)

Simple Factory vs Factory Method

Simple Factory = статичний метод. Не можна розширити наслідуванням. Factory Method = віртуальний метод у підкласі. Можна розширювати.

Modern Java реалізації

Factory Method → Supplier:

// Замість наслідування
public class Dialog {
    private final Supplier<Button> buttonFactory;

    public Dialog(Supplier<Button> buttonFactory) {
        this.buttonFactory = buttonFactory;
    }

    public void render() {
        Button button = buttonFactory.get();
        button.render();
    }
}

// Використання
Dialog dialog = new Dialog(WinButton::new);

Abstract Factory → Records:

// Record для зберігання сімейства
public record GUIFactory(
    Supplier<Button> button,
    Supplier<Checkbox> checkbox,
    Supplier<Menu> menu
) {}

// Заздалегідь визначені фабрики
public static final GUIFactory WINDOWS = new GUIFactory(
    WinButton::new, WinCheckbox::new, WinMenu::new
);

public static final GUIFactory MAC = new GUIFactory(
    MacButton::new, MacCheckbox::new, MacMenu::new
);

Spring Context як Meta-Factory

// Spring сам вирішує, які біни створити
@Configuration
public class DatabaseConfig {

    @Bean
    @Profile("mysql")
    public DatabaseFactory mysqlFactory() {
        return new MySQLFactory();
    }

    @Bean
    @Profile("postgres")
    public DatabaseFactory postgresFactory() {
        return new PostgresFactory();
    }
}

// Spring = Abstract Factory на стероїдах
// + Profile = вибір сімейства на runtime

Performance Implications

Factory Method:

Monomorphic call (1 підклас) → інлайнинг
Bimorphic call (2 підкласи) → все ще швидко
Megamorphic call (>2 підкласи) → непрямий виклик

Abstract Factory:

Додатковий рівень опосередкування:
  client → factory interface → concrete factory → product

Накладні витрати: 2 віртуальні виклики замість 1

Production Experience

Реальний сценарій: Abstract Factory врятував від mixing

  • UI фреймворк: Windows і Mac теми
  • Проблема: розробники змішували компоненти
  • Рішення: Abstract Factory гарантує сумісність
  • Результат: неможливо створити MacButton у Windows темі

Best Practices

  1. Factory Method для одного розширюваного продукту
  2. Abstract Factory для сімейства пов’язаних продуктів
  3. **Supplier** замість Factory Method у Modern Java

**Supplier** — Java 8+ функціональний інтерфейс, повертає об'єкт без параметрів.

  1. Spring Profiles замість ручних Abstract Factory
  2. Уникайте Abstract Factory якщо сімейство одне
  3. Пам’ятайте про OCP violation при розширенні Abstract Factory
  4. Record для зберігання сімейства фабрик

Records — Java 16+ компактні immutable класи.

Резюме для Senior

  • Factory Method = наслідування, один продукт, OCP-friendly
  • Abstract Factory = композиція, сімейство продуктів, OCP violation
  • Еволюція: Simple → Factory Method → Abstract Factory → DI
  • Modern Java: Supplier замінює Factory Method
  • Spring = Meta-Factory з Profiles
  • OCP Trade-off: Додавання нового продукту в Abstract Factory вимагає зміни ВСІХ реалізацій фабрик. Якщо фабрик 10 — 10 файлів потрібно правити.
  • Guarantee: Abstract Factory запобігає mixing продуктів

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Factory Method створює один продукт через наслідування, Abstract Factory — сімейство продуктів через композицію
  • Factory Method дотримується OCP (новий підклас не змінює код), Abstract Factory порушує OCP при додаванні нового продукту
  • Еволюція: Simple Factory → Factory Method → Abstract Factory → DI Container (Spring)
  • У Modern Java: Factory Method замінюється Supplier, Abstract Factory — Records або Map<Enum, Supplier>
  • Spring Context = Meta-Factory з @Profiles для вибору сімейства на runtime
  • Megamorphic calls: >2 підкласів у Factory Method → JIT не може інлайнити
  • Abstract Factory гарантує: продукти з різних сімейств НЕ змішуються

Часті уточнювальні запитання:

  • Коли обрати Factory Method, а коли Abstract Factory? — Один продукт = Factory Method, сімейство пов’язаних = Abstract Factory
  • Чому Abstract Factory порушує OCP? — Додавання нового продукту вимагає зміни інтерфейсу → ВСІ реалізації потрібно правити
  • Чим Simple Factory відрізняється від Factory Method? — Simple Factory = статичний метод (не можна розширити), Factory Method = віртуальний метод у підкласі
  • Як Spring замінює Abstract Factory? — @Configuration + @Profile = вибір сімейства бінів на runtime

Червоні прапорці (НЕ говорити):

  • “Я використовую Abstract Factory для одного продукту” — overengineering, достатньо Factory Method
  • “Factory Method і Abstract Factory — це одне й те саме” — принципово різні механізми і цілі
  • “Abstract Factory дотримується OCP” — порушує при додаванні нового продукту
  • “У Spring не потрібні Factory патерни” — Spring сам використовує Abstract Factory під капотом

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

  • [[8. Коли використовувати Builder]] — породжуючий патерн для складних об’єктів
  • [[9. Що таке патерн Prototype]] — породжуючий патерн копіювання
  • [[1. Що таке патерни проектування]] — загальний вступ
  • [[2. Які категорії патернів існують]] — Creational патерни
  • [[10. Коли використовувати Strategy]] — поведінковий патерн, альтернатива через композицію