Гид по технологиям

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

7 min read JavaScript Обновлено 03 Apr 2026
Дизайн‑паттерны JS: модуль и фабрика
Дизайн‑паттерны JS: модуль и фабрика

Иллюстрация основных концепций модульного и фабричного паттернов в 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 (строитель) или композицию.
  • Нужна наследуемость с полиморфными методами — рассмотрите классы с прототипной цепочкой.

Практическая методология внедрения паттернов

  1. Определите проблему: зачем нужен паттерн (инкапсуляция, создание объектов, разделение ответственности).
  2. Начните с простого варианта (функция или объект) и протестируйте на небольших данных.
  3. Внедрите интерфейс (публичные методы), покройте тестами и задокументируйте API.
  4. Если нужно — замените реализацию (например, IIFE на ES‑модуль) без изменения внешнего API.
  5. Рефакторьте, когда появятся повторяющиеся шаблоны.

Чеклист для разработчика и команды

Для автора модуля:

  • Есть ли у модуля единственная зона ответственности?
  • Экспортируется только необходимое публичное 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 для добавления нового модуля в проект

  1. Описать цель модуля и публичный интерфейс в README.
  2. Создать файл модуля (ESM) с экспортируемыми функциями/классами.
  3. Написать юнит‑тесты для основных сценариев.
  4. Добавить документацию примеров использования.
  5. Провести код‑ревью и согласовать на архитектурной встрече.

Шаблоны и сниппеты для быстрого старта

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

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Play Something на Netflix: как пользоваться
Стриминг

Play Something на Netflix: как пользоваться

Как пользоваться Disney+ GroupWatch
Стриминг

Как пользоваться Disney+ GroupWatch

Как скрыть визуальные элементы macOS
macOS

Как скрыть визуальные элементы macOS

Ошибка доступа к файлу в Windows — устранение
Windows ошибки

Ошибка доступа к файлу в Windows — устранение

Установка шрифтов в Microsoft Office (Windows 10)
Microsoft Office

Установка шрифтов в Microsoft Office (Windows 10)

Поле «Вложения» в Airtable — руководство
Airtable

Поле «Вложения» в Airtable — руководство