Вопрос 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 pattern]] — порождающий паттерн копирования
  • [[1. Что такое паттерны проектирования]] — общее введение
  • [[2. Какие категории паттерны существуют]] — Creational паттерны
  • [[10. Когда использовать Strategy]] — поведенческий паттерн, альтернатива через композицию