В чём разница между 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):
- Легко добавлять новые семейства продуктов (✅ OCP соблюдается):
Чтобы добавить тему Linux, достаточно создать
LinuxFactory, реализующуюGUIFactory, и классыLinuxButton,LinuxCheckbox. Существующий код приложения не меняется. - Крайне тяжело добавлять новые типы продуктов (❌ OCP нарушается):
Если в UI требуется добавить компонент
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> и 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 каверзных вопроса с ответами
- Почему добавление нового продукта в 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()) в конкретных классах фабрик часто реализуются именно как фабричные методы (Factory Method).
- Ответ: Да. На практике методы интерфейса Abstract Factory (например,
- Как 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-контейнера и аннотаций конфигурации.
Связанные темы
- Когда использовать Builder — альтернатива для поэтапного конструирования сложных объектов
- Что такое Prototype pattern — создание объектов клонированием
- Что такое паттерны проектирования — обзор и основы
- Какие категории паттернов существуют — классификация GoF
- Зачем вообще нужны принципы SOLID — принципы OCP и DIP