В чому різниця між Factory Method та Abstract Factory?
Обидва патерни допомагають створювати об'єкти, але на різних рівнях:
🟢 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 | ✅ Дотримується | ❌ Порушується при додаванні продукту |
Типові помилки
- Abstract Factory для одного продукту
// ❌ Overengineering interface ButtonFactory { Button create(); } // ✅ Достатньо Factory Method abstract class Dialog { abstract Button createButton(); } - Порушення 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
- Factory Method для одного розширюваного продукту
- Abstract Factory для сімейства пов’язаних продуктів
- **Supplier
** замість Factory Method у Modern Java
**Supplier
- Spring Profiles замість ручних Abstract Factory
- Уникайте Abstract Factory якщо сімейство одне
- Пам’ятайте про OCP violation при розширенні Abstract Factory
- 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]] — поведінковий патерн, альтернатива через композицію