Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке патерн Factory (Фабрика)?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Factory (Фабрика)** - патерн, що централізує логіку `new` в одному місці. Код передає тип, фабрика вирішує, який клас інстанціювати. ```javascript const notif = NotificationFactory.create('email', 'Привіт!'); notif.send(); ``` **Ключове:** використовуй, коли тип об'єкта залежить від рядка конфігурації або коли треба підмінити реалізацію без зміни коду, що викликає.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Factory (Фабрика)** - патерн створення об'єктів, що ховає логіку `new` за функцією або методом, щоб решта коду не знала, який саме клас інстанціювати. ## Теорія ### TL;DR - Factory централізує `new` в одному місці; код просить об'єкт за типом, а не за назвою класу - Корисний, коли тип об'єкта залежить від рядка конфігурації, ролі користувача або змінної середовища - Три форми: Simple Factory (один статичний метод), Factory Method (підклас перевизначає `create()`), Abstract Factory (сімейство пов'язаних об'єктів) - Компроміс: Simple Factory потребує редагування при кожному новому типі; Factory Method вирішує це через підкласи ### Швидкий приклад ```javascript // Без фабрики - код знає про кожен конкретний клас const user = role === 'admin' ? new AdminUser(data) : new RegularUser(data); // З фабрикою - код знає тільки про фабрику const user = UserFactory.create(role, data); ``` ### Як працює Simple Factory Simple Factory - це статичний метод або звичайна функція, що приймає рядок-тип і повертає потрібний об'єкт. Жодний конкретний клас не потрапляє у викликаючий код. ```javascript class NotificationFactory { static create(channel, message) { switch (channel) { case 'email': return new EmailNotification(message); case 'sms': return new SMSNotification(message); case 'push': return new PushNotification(message); default: throw new Error(`Невідомий канал: ${channel}`); } } } const notif = NotificationFactory.create('email', 'Привіт!'); notif.send(); // EmailNotification відправляє ``` Весь `switch` живе в одному файлі. Додаєш новий канал - змінюєш один клас, а не шукаєш по всій кодовій базі. ### Патерн Factory Method Simple Factory - поширена конвенція, але в книзі Gang of Four його немає. Factory Method - це вже офіційний GoF-патерн. Базовий клас визначає метод на кшталт `createProduct()`, і кожен підклас перевизначає його, щоб повертати свій тип об'єкта. ```javascript class Dialog { createButton() { throw new Error('createButton must be implemented'); } render() { const button = this.createButton(); // factory method button.onClick(() => this.closeDialog()); button.render(); } } class WindowsDialog extends Dialog { createButton() { return new WindowsButton(); } } class WebDialog extends Dialog { createButton() { return new HTMLButton(); } } ``` Метод `render()` у базовому класі не знає, з яким типом кнопки працює. Новий тип платформи - новий підклас. `Dialog` не чіпаємо. ### Коли використовувати - Тип об'єкта залежить від значення конфігурації, вибору користувача або змінної середовища - Треба замінити реалізацію без зміни коду, що викликає (зручно в тестах: фабрика повертає mock, реальний код не змінюється) - Є кілька класів з однаковим інтерфейсом, але різною поведінкою - Пишеш бібліотеку, де користувач має підключати власні реалізації Найпростіший сигнал на практиці: якщо код вже має `if/else` або `switch` для вибору класу, ця логіка має жити у фабриці, а не розпорошуватись по фічах. Не додавай фабрику просто тому, що є два класи. Якщо завжди створюється один і той самий тип, `new` - правильний вибір. ### Типові помилки **Помилка 1: Фабрика без розгалуження** ```javascript // Зайвий рівень абстракції без жодної користі class UserFactory { static create(data) { return new User(data); // завжди один клас } } ``` Якщо логіка не розгалужується, фабрика нічого не дає. **Помилка 2: Об'єкти з різними інтерфейсами** ```javascript class AnimalFactory { static create(type) { if (type === 'dog') return new Dog(); // є .bark() if (type === 'cat') return new Cat(); // є .meow() // код все одно мусить перевіряти тип - фабрика не допомогла } } ``` Всі об'єкти з фабрики мають підтримувати один інтерфейс. Якщо `Dog` і `Cat` мають різний API, код повертається до перевірки типів вручну. **Помилка 3: Немає гілки `default`** ```javascript static create(type) { switch (type) { case 'admin': return new AdminUser(); case 'guest': return new GuestUser(); // немає default - повертає undefined без жодної помилки } } ``` Завжди кидай помилку при невідомому типі. Мовчазний `undefined` дає заплутані баги пізніше. **Помилка 4: Плутанина Simple Factory і Factory Method** Це різні речі. Simple Factory - зручна конвенція. Factory Method - формальний GoF-патерн на основі наслідування. На інтерв'ю уточни, що саме мається на увазі, перш ніж відповідати. ### Де зустрічається в реальному коді - React: `React.createElement()` - фабрична функція. Передаєш рядок або клас компонента, отримуєш елемент virtual DOM. - Node.js `http`: `http.createServer()` абстрагує конструктор і повертає Server. - Express: `express()` сам по собі фабрика, що повертає Application. - Jest: `jest.fn()` повертає налаштовану mock-функцію. - Angular DI: інжектор використовує фабричні функції для створення сервісів. ### Follow-up питання **Q:** Яка різниця між Simple Factory і Factory Method? **A:** Simple Factory - статичний метод або функція, що обирає клас і викликає `new`. Factory Method - GoF-патерн, де базовий клас визначає метод, який підкласи перевизначають. Simple Factory потребує редагування одного файлу при новому типі; Factory Method - додавання нового підкласу. **Q:** Чи можна писати фабрику без класу? **A:** Так, звичайна функція підходить. `function createUser(role, data) { ... }` - це теж фабрика. Клас нічого не додає, крім простору імен. Багато JavaScript-проєктів обирають прості фабричні функції замість статичних методів. **Q:** Як фабрика допомагає в тестах? **A:** Фабрику передають як залежність замість прямого виклику `new` у бізнес-логіці. У тестах підміняють фабрику, щоб вона повертала mock. Код, що тестується, не знає різниці. **Q:** Коли обирати Factory Method замість Simple Factory? **A:** Коли нові типи з'являються часто, або коли зовнішній код має визначати власні типи без зміни вихідників. Factory Method дозволяє розширювати без редагування наявного коду. **Q:** Що таке Abstract Factory і коли він потрібен? **A:** Abstract Factory створює сімейства пов'язаних об'єктів. Замість одного методу - кілька методів створення. `WindowsUIFactory` створює `WindowsButton` і `WindowsScrollbar` разом. Використовується, коли об'єкти мають бути узгоджені між собою. ## Приклади ### Базовий: створення фігур ```javascript class Circle { constructor(radius) { this.radius = radius; } area() { return Math.PI * this.radius ** 2; } } class Rectangle { constructor(w, h) { this.width = w; this.height = h; } area() { return this.width * this.height; } } function createShape(type, ...args) { if (type === 'circle') return new Circle(...args); if (type === 'rectangle') return new Rectangle(...args); throw new Error(`Невідома фігура: ${type}`); } const shapes = [ createShape('circle', 5), createShape('rectangle', 4, 6), ]; shapes.forEach(s => console.log(s.area())); // 78.53... // 24 ``` Обидві фігури мають `.area()`. Код у `forEach` не знає конкретного типу за кожним об'єктом. ### Практичний: платіжні системи ```javascript class StripeProcessor { pay(amount) { console.log(`Stripe: списуємо $${amount}`); } } class PayPalProcessor { pay(amount) { console.log(`PayPal: відправляємо $${amount}`); } } class CryptoProcessor { pay(amount) { console.log(`Crypto: переказуємо $${amount} у BTC`); } } class PaymentFactory { static create(method) { const map = { stripe: StripeProcessor, paypal: PayPalProcessor, crypto: CryptoProcessor, }; const Processor = map[method]; if (!Processor) throw new Error(`Не підтримується: ${method}`); return new Processor(); } } // Метод приходить з вибору користувача const processor = PaymentFactory.create(getUserSelectedMethod()); processor.pay(99.99); ``` Код оформлення замовлення імпортує одну фабрику, а не три класи процесорів. Зміна платіжного провайдера торкається одного рядка всередині `PaymentFactory`, нічого більше. ### Розширений: фабрика з реєстром ```javascript class PluginFactory { static #registry = new Map(); static register(name, Constructor) { this.#registry.set(name, Constructor); } static create(name, ...args) { const Constructor = this.#registry.get(name); if (!Constructor) throw new Error(`Плагін "${name}" не зареєстрований`); return new Constructor(...args); } } // Вбудовані плагіни реєструються при старті PluginFactory.register('logger', LoggerPlugin); PluginFactory.register('cache', CachePlugin); // Сторонній плагін розширює систему без зміни PluginFactory PluginFactory.register('analytics', AnalyticsPlugin); const logger = PluginFactory.create('logger', { level: 'debug' }); ``` Жодного `switch` для редагування. Нові типи реєструються самостійно. Цей підхід зустрічається в Webpack loaders, Babel plugins і будь-яких інших системах з підтримкою плагінів.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.