Дизайн‑паттерны в JavaScript: Модуль и Фабрика

Что такое дизайн‑паттерн в JavaScript
Дизайн‑паттерн — это шаблон решения повторяющейся задачи в разработке. В JavaScript паттерны могут применяться к коду разного масштаба: от отдельных функций до целых архитектурных модулей. Паттерн помогает структуре кода быть предсказуемой, тестируемой и удобной для командной работы.
Краткое определение терминов:
- Инкапсуляция: сокрытие деталей реализации и открытие только нужного API.
- API модуля: публичные методы и свойства, через которые другие части приложения взаимодействуют с модулем.
- Фабрика: функция или объект, ответственный за создание и конфигурирование объектов одного типа.
Основной сценарий: зачем применять паттерны
Почему стоит знакомиться с паттернами:
- Повторное использование кода.
- Лёгкая замена реализации (swap implementation) без изменения внешнего API.
- Чёткое разделение ответственности.
- Удобство тестирования и отладки.
Важно: паттерны — это не догма. Они должны приносить пользу: уменьшать сложность и дублирование кода. Если паттерн усложняет простую задачу, лучше выбрать более простой подход.
Модульный паттерн
JavaScript‑модули появились ещё в эпоху ES5 через практику IIFE (immediately invoked function expression) и позже через стандартизованные ES‑модули (import/export). Модульный паттерн даёт приватность и явный публичный интерфейс.
Базовая структура модульного паттерна
(function(){
// Declare private variables and functions
// Declare and return public variables and functions
})();
В этой конструкции модуль сразу выполняется при создании (IIFE). Внутри есть зона приватных данных и функция/объект, который возвращается наружу — это публичное API.
Пример: базовый UI‑модуль (оригинальный пример)
const UIController = (function() {
// the private section of the module
let component = 'Replacement Text';
const changeComponent = function() {
// change all the h1 text to what is in the component variable above
const element = document.querySelector('h1');
element.textContent = component;
}
// the public section of the module
return {
callChangeComponent: function() {
changeComponent();
}
}
})();
Внутри changeComponent и переменная component приватны. Единственный способ вызвать изменение — через UIController.callChangeComponent(). Это обеспечивает контроль и предотвращает непреднамеренные вмешательства.
Неправильный и правильный вызов
Неправильно:
UIController.changeComponent();
Правильно:
UIController.callChangeComponent();
Ошибка при первом вызове объясняется тем, что changeComponent не экспортируется наружу.
Современная альтернатива: ES‑модули (import/export)
ES‑модули дают встроенную в язык систему модулей — без IIFE и с явным синтаксисом import/export. Они поддерживают статический анализ и дерево зависимостей.
Пример экспорта:
// uiController.js
let component = 'Replacement Text';
function changeComponent() {
const element = document.querySelector('h1');
element.textContent = component;
}
export function callChangeComponent() {
changeComponent();
}
export function setComponent(value) {
component = value;
}И импорт:
import { callChangeComponent, setComponent } from './uiController.js';
setComponent('New text');
callChangeComponent();ES‑модули лучше подходят для современных сборок и инструментов (Webpack, Vite, Rollup). Они обеспечивают ленивую загрузку и лучшие возможности tree shaking.
Когда модульный паттерн не лучшая идея
- Простой скрипт из одной функции: дополнительная обёртка усложнит чтение.
- Когда нужна наследуемость и полиморфизм — лучше использовать классы или фабрики, комбинируя паттерны.
- Если нужна динамическая загрузка кода и слабая связность — рассматривайте micro‑frontend или динамические imports().
Важно: модуль не решит проблем плохого разделения ответственности. Архитектурные принципы (SRP — Single Responsibility Principle) остаются ключевыми.
Фабричный паттерн
Фабрика — это подход к созданию объектов. Вместо прямого вызова конструктора код обращается к фабрике, которая решает, какой конкретный тип объекта создать и какие свойства ему назначить.
Применимость: когда есть коллекция похожих объектов с небольшими вариациями (например, задачи с типами urgent/trivial).
Оригинальный пример фабрики для задач
// Factory pattern function
const TaskFactory = function() {
this.createTask = function(name, type) {
let task;
// check the type the user selected
if (type === 'urgent') {
task = new UrgentTask(name);
} else if(type === 'trivial') {
task = new TrivialTask(name);
}
// set the type selected in the if statement to the one received as a property
task.type = type;
// used to print the task and its type to the console
task.define = function() {
console.log(`${this.name} (${this.type}): ${this.priority}`)
}
return task
}
}
Типы задач:
// Create the urgent task type
const UrgentTask = function(name) {
this.name = name;
this.priority = "as soon as possible"
}
// create the trivial task type
const TrivialTask = function(name) {
this.name = name;
this.priority = "when you can"
}
Создаём массив и объекты через фабрику:
// create an array to host the different task
const task = [];
// create two new tasks
const factory = new TaskFactory();
task.push(factory.createTask('Clean the house', 'urgent'));
task.push(factory.createTask('Reach level 30 in Candy Crush', 'trivial'));
// print each task to the console
task.forEach(function(task){
task.define();
});
Ожидаемый вывод в консоли:
Clean the house (urgent): as soon as possible
Reach level 30 in Candy Crush (trivial): when you can
Современные альтернативы фабричному паттерну
- Использование классов и наследования через ES6 class.
- Функции‑фабрики (factory functions) без new, возвращающие объект.
- Комбинация фабрики и прототипов для общих методов и экономии памяти.
Пример функции‑фабрики:
function createTask(name, type) {
const base = { name, type };
if (type === 'urgent') {
return Object.assign(base, { priority: 'as soon as possible' });
}
return Object.assign(base, { priority: 'when you can' });
}
const tasks = [createTask('Clean the house', 'urgent'), createTask('Play game', 'trivial')];Эта запись проще и избегает проблем с new и this.
Когда фабрика не подходит
- Число вариантов растёт экспоненциально — фабрика становится громоздкой.
- Требуется сложная логика конфигурации — лучше использовать builder (строитель) или композицию.
- Нужна наследуемость с полиморфными методами — рассмотрите классы с прототипной цепочкой.
Практическая методология внедрения паттернов
- Определите проблему: зачем нужен паттерн (инкапсуляция, создание объектов, разделение ответственности).
- Начните с простого варианта (функция или объект) и протестируйте на небольших данных.
- Внедрите интерфейс (публичные методы), покройте тестами и задокументируйте API.
- Если нужно — замените реализацию (например, IIFE на ES‑модуль) без изменения внешнего API.
- Рефакторьте, когда появятся повторяющиеся шаблоны.
Чеклист для разработчика и команды
Для автора модуля:
- Есть ли у модуля единственная зона ответственности?
- Экспортируется только необходимое публичное API?
- Приватные переменные не доступны снаружи?
- Есть ли юнит‑тесты для публичных методов?
Для фабрики:
- Фабрика покрывает все ожидаемые типы объектов?
- Логика выбора типа достаточно простая и понятная?
- Экземпляры легко сериализуются/десериализуются (если нужно)?
- Нет дублирования кода между типами — общие части вынесены?
Для команды:
- Документация API и примеры использования?
- Линтер/пресеты согласованы на всём проекте?
- Решение согласовано в код‑ревью и отражено в архитектурных заметках?
Ментальные модели и эвристики
- «API сначала»: сначала продумайте публичный интерфейс модуля, затем внутреннюю реализацию.
- «Приватное по‑умолчанию»: не делайте ничего публичным, пока в этом нет реальной потребности.
- «Фабрика для вариаций, класс для поведения»: если объекты отличаются в основном данными — используйте фабрику; если в поведении — используйте классы или прототипы.
Критерии приёмки
- Модуль выполняет только заявленную функцию и имеет покрытие тестами минимум на 80% критичных сценариев.
- Публичный API стабилен и документирован.
- Фабрика корректно создаёт все допустимые варианты объектов и возвращает валидные экземпляры для последующей обработки.
Примеры отказоустойчивости и отладки
- Логирование ошибок при невозможности создать объект в фабрике.
- Фолбэк‑поведение: фабрика возвращает объект с дефолтными значениями, если тип не распознан.
Пример фолбэка в фабрике:
function createTaskSafe(name, type) {
switch (type) {
case 'urgent': return { name, type, priority: 'as soon as possible' };
case 'trivial': return { name, type, priority: 'when you can' };
default: return { name, type: 'unknown', priority: 'unspecified' };
}
}Совместимость и миграция
- Если проект использует сборщик, переход с IIFE на ES‑модули минимален и рекомендуется для новых проектов.
- Для библиотек, распространяемых через CDN, стоит поддерживать UMD или ESM + CJS‑билды.
- Для браузеров без модулей можно транспилировать код через Babel.
Безопасность и приватность
- Никогда не храните секреты (ключи, пароли) в клиентских модулях — это не безопасно.
- Для серверных модулей используйте переменные окружения и секреты в защищённом хранилище.
Фактбокс: ключевые даты и понятия
- ES5 (2009): широкое распространение IIFE и модульных практик.
- ES6 (ES2015): стандартизованные модули import/export и ключевое слово class.
- IIFE: паттерн, использующий немедленный вызов функции для создания приватной области видимости.
- Factory: паттерн для централизованного создания объектов с разной конфигурацией.
Мини‑SOP для добавления нового модуля в проект
- Описать цель модуля и публичный интерфейс в README.
- Создать файл модуля (ESM) с экспортируемыми функциями/классами.
- Написать юнит‑тесты для основных сценариев.
- Добавить документацию примеров использования.
- Провести код‑ревью и согласовать на архитектурной встрече.
Шаблоны и сниппеты для быстрого старта
Revealing Module (вариант модульного паттерна):
const Counter = (function() {
let count = 0;
function increment() { count++; }
function value() { return count; }
return {
increment,
value
};
})();
Counter.increment();
console.log(Counter.value());Фабрика с прототипом для общих методов:
const TaskProto = {
define() {
console.log(`${this.name} (${this.type}): ${this.priority}`);
}
};
function TaskFactory(name, type) {
const base = Object.create(TaskProto);
base.name = name;
base.type = type;
base.priority = type === 'urgent' ? 'as soon as possible' : 'when you can';
return base;
}Glossary — 1‑строчный словарь
- IIFE: немедленно вызываемая функциональная выражение, использующаяся для локальной области видимости.
- ES‑модуль: встроенный механизм import/export в JavaScript.
- Фабрика: центр создания объектов определённого типа.
- Revealing Module: модуль, который явно возвращает публичные члены.
Риски и способы смягчения
Риск: чрезмерная сложность архитектуры. Митигирование: придерживаться KISS (keep it simple), документировать решения и рефакторить по мере роста приложения.
Риск: утечки памяти из-за глобальных ссылок. Митигирование: использовать слабые ссылки или отслеживать слушателей событий при уничтожении модулей.
Короткое руководство по тестам (примеры тестов)
- Тест для модуля: проверка публичного API — мок DOM при необходимости.
- Тест для фабрики: проверка созданных объектов на ожидаемую структуру и поведение.
Пример теста (псевдо‑код):
describe('TaskFactory', () => {
it('creates urgent tasks with correct priority', () => {
const f = new TaskFactory();
const t = f.createTask('Test', 'urgent');
expect(t.priority).toBe('as soon as possible');
});
});Короткое резюме
Два рассмотренных паттерна решают разные задачи: модуль отвечает за инкапсуляцию и чистый API, фабрика — за централизованное создание похожих объектов. В современных проектах чаще всего используют ES‑модули и функции‑фабрики или классы в зависимости от потребностей. Выбирайте паттерн согласно задаче, документируйте интерфейсы и проверяйте поведение через тесты.
Важно
Паттерны — это инструменты. Главное — понятная архитектура, тесты и договорённости в команде.
Image Credit: Alltechbuzz/ Pixabay
Похожие материалы
Play Something на Netflix: как пользоваться
Как пользоваться Disney+ GroupWatch
Как скрыть визуальные элементы macOS
Ошибка доступа к файлу в Windows — устранение
Установка шрифтов в Microsoft Office (Windows 10)