🧩 Раздел 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 {
    // Бизнес-логика опирается на созданный продукт
    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"); } }

// Абстрактная фабрика семейства UI-компонентов
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): объект фабрики передается клиенту в качестве зависимости.
Что создает Один полиморфный продукт. Семейство согласованных продуктов.
Точка расширения Добавление нового класса-создателя и продукта. Создание нового класса фабрики для нового семейства.
Связанность с клиентом Клиент часто сам является базовым классом (Template Method). Клиент отделен от фабрики и взаимодействует с ней через интерфейс.

Пример использования Abstract Factory клиентом

Клиентский код полностью изолирован от конкретных классов UI:

public class Application {
    private final Button button;
    private final Checkbox checkbox;

    // Фабрика внедряется через конструктор (композиция / DI)
    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 (требует правки метода при добавлении типа).

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 нарушается): Если в UI требуется добавить компонент 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> и Function References

В современной Java (Java 8+) тяжелые иерархии классов с Factory Method часто заменяются функциональными интерфейсами:

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 лаконично описывается через record:

public record GuiComponents(
    Supplier<Button> buttonSupplier,
    Supplier<Checkbox> checkboxSupplier
) {}

// Конфигурация семейств:
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-приложениях на базе Spring необходимость в ручной реализации Abstract Factory практически отпадает:

  • BeanFactory / ApplicationContext выступает в роли универсальной Abstract Factory.
  • Выбор семейств компонентов осуществляется декларативно через профили (@Profile("dev") vs @Profile("prod")) или условия (@ConditionalOnProperty).
@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()) в конкретных классах фабрик часто реализуются именно как фабричные методы (Factory Method).
  4. Как Spring Framework реализует концепцию Abstract Factory?
    • Ответ: Сам ApplicationContext является реализацией фабрики компонентов. Механизм @Bean-методов в @Configuration-классах в сочетании с аннотациями @Profile и @Conditional позволяет переключать целые семейства зависимостей (например, интеграция с Kafka/Postgres в продакшене и H2/Mock-очереди в тестах) без изменения бизнес-кода.

Типичные ошибки и Red Flags

  • 🚩 Использование Abstract Factory для создания одного продукта — явный признак overengineering, усложняющий архитектуру без практической пользы (достаточно Factory Method или обычного Supplier).
  • 🚩 Смешивание понятий Simple Factory и Factory Method — путаница между статическим методом с оператором switch и настоящим полиморфным паттерном GoF.
  • 🚩 Игнорирование риска нарушения OCP при часто меняющемся наборе продуктов в Abstract Factory.
  • 🚩 Создание жестких фабричных иерархий в Spring-приложениях вместо использования стандартного DI-контейнера и аннотаций конфигурации.

Связанные темы