Abstract Factory Pattern — Абстрактная фабрика
Фабрика фабрик: семейство связанных объектов под несколько платформ. Универсальный код не должен знать, под какой платформой работает.
Проблема
Приложение должно работать на нескольких платформах (iOS, Android, Web, Desktop) с разными визуальными контролами. Нужно писать клиентский код один раз — он не знает, какие конкретно классы создаёт. Паттерн обеспечивает совместимость создаваемых объектов: если использовали «тёмную тему», то и кнопки, и поля ввода будут тёмными.
Где используется
- UI-библиотеки: семейство компонентов для разных тем (светлая/тёмная) или платформ (web/native)
- Кросс-платформенные приложения: Windows vs macOS
- Базы данных: фабрики для MySQL/PostgreSQL с одинаковым API
- Тестирование: замена реальных сервисов на mock через фабрику
- Плагинные системы: динамический выбор набора компонентов
Решение
Один класс с пачкой методов-конструкторов:
createButton,createInput,createProgressBar...- Каждая платформа — своя реализация фабрики
- Клиент получает фабрику, а не конкретные классы
Реализации
Классическая (на классах)
class UIFactory {
createButton { throw new Error('abstract'); }
createInput { throw new Error('abstract'); }
}
class LightThemeFactory extends UIFactory {
createButton { return { render: => '<button class="btn-light">Click</button>' }; }
createInput { return { render: => '<input class="input-light" />' }; }
}
class DarkThemeFactory extends UIFactory {
createButton { return { render: => '<button class="btn-dark">Click</button>' }; }
createInput { return { render: => '<input class="input-dark" />' }; }
}
// Клиентский код — работает с любой фабрикой
function renderForm(factory) {
const button = factory.createButton;
const input = factory.createInput;
return `<form>${input.render}${button.render}</form>`;
}
const theme = localStorage.getItem('theme') === 'dark'
? new DarkThemeFactory
: new LightThemeFactory();
console.log(renderForm(theme));
Функциональный стиль (объект-словарь)
const factories = {
mysql: {
createConnection: (cfg) => ({ type: 'mysql', ...cfg }),
createQuery: (sql) => ({ type: 'mysql', sql }),
},
postgres: {
createConnection: (cfg) => ({ type: 'pg', ...cfg }),
createQuery: (sql) => ({ type: 'pg', sql: sql.replace('LIMIT', 'FETCH FIRST') }),
}
};
function getDBFactory(driver) {
const f = factories[driver];
if (!f) throw new Error(`Unknown driver: ${driver}`);
return f;
}
const db = getDBFactory(process.env.DB_DRIVER || 'mysql');
Где используется в JS-экосистеме
- React Native + платформо-специфичные компоненты
- Web Components: разные реализации под разные браузеры/режимы
- Electron vs браузер: разные API доступа к файловой системе
- Тестовые моки: фабрика mock'ов для test-окружения
Подводные камни
- Избыточен при одном семействе — если платформа одна и не планируется добавлять — лишний слой.
- Разрастание фабрик: каждое новое семейство требует новой конкретной фабрики со всеми методами.
- Смешение Factory Method и Abstract Factory: Factory Method создаёт один продукт через наследование; Abstract Factory — семейство продуктов через композицию.
- Все методы-конструкторы должны быть согласованы между фабриками (LSP).
- В JS часто избыточен — динамический выбор класса проще через объект-реестр.
Главные тезисы автора
- «Несколько семейств, связанных абстракцией» — ключевая фраза.
- «Универсальный код не должен знать, где он запущен» — главная мотивация.
- В JS класс — абстракция первого порядка, можно класть в переменную и вызывать
new— это упрощает реализацию.
🎓 Источники
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- Пример с UI-контролами под iOS/Android/Windows
- Класс с пачкой методов-конструкторов
- Refactoring Guru — Abstract Factory
См. также
- Factory Pattern
- Strategy Pattern — коллекция стратегий = другая форма
- Bridge Pattern — тоже про разделение иерархий
- Builder Pattern
- _MOC GoF