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

Паттерны проектирования в JavaScript: модуль и фабрика — практическое руководство

7 min read JavaScript Обновлено 30 Dec 2025
Паттерны JavaScript: модуль и фабрика
Паттерны JavaScript: модуль и фабрика

Иллюстрация: концепция паттернов JavaScript

Что такое паттерн проектирования в JavaScript

Паттерн проектирования — это повторяемое решение распространённой задачи в программировании. В JavaScript паттерны помогают:

  • структурировать код;
  • уменьшать связанность компонентов;
  • улучшать повторное использование и тестируемость.

Кратко: паттерн — это не библиотека, а способ организации кода. Он может применяться к отдельным функциям, модулям или архитектуре всего приложения.

Important: паттерн не заменяет архитектуру — он дополняет её. Выбор паттерна зависит от требований к инкапсуляции, масштабируемости и простоте тестирования.

Модульный паттерн

Модульный паттерн пригоден, когда нужно закрыть часть реализации и выставить ограниченный публичный API. В классическом ES5-стиле это достигается через IIFE (Immediately Invoked Function Expression).

Базовая структура модуля

(function(){
    // Declare private variables and functions
    // Declare and return public variables and functions
})();

Здесь есть две зоны:

  • приватная — локальные переменные и функции, недоступные извне;
  • публичная — объект, возвращаемый из IIFE, который содержит методы и поля, доступные внешнему коду.

Плюсы модульного паттерна:

  • инкапсуляция состояния;
  • предотвращение загрязнения глобальной области видимости;
  • явный контракт через возвращаемый объект.

Минусы и ограничения:

  • в классическом варианте обновление кода в рантайме затруднено (статический IIFE);
  • сложнее писать модульные тесты без явного экспорта (в ES6 это решается экспоротом);
  • при неправильной организации публичного API вы можете нечаянно открыть слишком многое.

Пример: модуль UIController

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.changeComponent(), но может вызвать UIController.callChangeComponent(), который косвенно вызывает приватную функцию.

Неправильный вызов (ошибка):

UIController.changeComponent();

Правильный вызов:

UIController.callChangeComponent();

Когда лучше использовать модуль

  • когда нужно скрыть состояние (например, счётчики, кэш);
  • для организации логики контроллеров в небольших приложениях;
  • когда нужно ограничить API и предотвратить случайные изменения.

Когда модуль не подходит

  • если нужен tree-shaking и интерактивный импорт/экспорт — предпочтительнее ES6-модули (import/export);
  • если требуется наследование и полиморфизм в классическом OOP-стиле — удобнее классы и фабрики;
  • если нужна лёгкая мокируемость в тестах без реэкспорта — применяйте dependency injection.

Notes: современные проекты чаще используют ES-модули (export / import) вместо IIFE. Они дают те же преимущества инкапсуляции, но в стандартной форме и с поддержкой инструментов сборки.

Паттерн фабрика

Фабрика (Factory / Factory Method) полезна, когда приложение должно создавать много похожих объектов с небольшими различиями: разные типы задач, уведомлений, элементов UI и т. п.

Основная идея: централизовать процесс создания объектов в функции/объекте, чтобы не дублировать логику создания по всему коду.

Пример фабрики для задач

// 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

Почему фабрика полезна

  • концентрирует логику создания объектов в одном месте;
  • упрощает добавление новых типов (достаточно добавить ветку или карту типов);
  • облегчает тестирование производства объектов;
  • позволяет применять стратегии создания (кеширование, пул объектов).

Ограничения фабрики

  • фабрика может превратиться в «бога» (God Object), если в ней сконцентрировать слишком много логики;
  • при большом количестве типов лучше использовать регистр типов (map) вместо цепочки if/else;
  • если объекты сложные, лучше применять билдер (Builder) или композицию.

Альтернативные подходы и эволюция паттернов

  1. ES6-модули (import / export)

    • позволяют явно экспортировать API и импортировать зависимости;
    • дают преимущества сборщиков и tree-shaking.
  2. Классы и наследование (ES6 class)

    • полезны при необходимости наследования и переопределения поведения;
    • классы совместимы с фабричным подходом (фабрика может возвращать экземпляры классов).
  3. Функции-фабрики (factory functions)

    • вместо new возвращают объект; легче тестировать и комбинировать с декораторами.
  4. Dependency Injection

    • передаёт зависимости извне, что улучшает тестируемость и гибкость.

Выбор зависит от требований: простота и изоляция — модуль/ES6-модуль; разные экземпляры — фабрика; сложное построение — билдер.

Ментальные модели и эвристики выбора

  • «Инкапсуляция» → выбирайте модуль / ES6-модуль.
  • «Много похожих объектов» → выбирайте фабрику.
  • «Нужно наследование/полиморфизм» → классы + фабрика/абстрактная фабрика.
  • «Хочу простоту и тестируемость» → функции-фабрики и dependency injection.

Mermaid диаграмма принятия решения:

flowchart TD
    A[Есть объекты с разными типами?] -->|Да| B[Нужен централизованный создатель?]
    A -->|Нет| C[Есть цель скрыть состояние?]
    B -->|Да| D[Фабрика]
    B -->|Нет| E[Простая конструкция без фабрики]
    C -->|Да| F[Модуль/ES6-модуль]
    C -->|Нет| E

Чеклист по внедрению (роль-based)

Разработчик:

  • описать публичный API модуля;
  • выделить приватные функции и состояние;
  • написать unit-тесты на публичные методы;
  • документировать контракты фабрики (входные параметры, возвращаемые свойства).

Технический лидер:

  • проверить согласованность подхода в проекте (модули vs ES-модули);
  • ограничить размер фабрики (не более X зон ответственности — эвристика: «одна фабрика — один набор типов»);
  • утвердить стратегию миграции к ES6, если нужно.

Ревьювер кода:

  • убедиться, что приватное состояние недоступно извне;
  • проверить отсутствие побочных эффектов в конструкторе фабрики;
  • убедиться, что публичный API минимален и предсказуем.

Критерии приёмки

  1. Инкапсуляция: приватные переменные недоступны извне (проверить через unit-тесты и попытки доступа).
  2. Поведение фабрики: при разных входах создаются корректные типы объектов.
  3. Контракты: публичные методы возвращают ожидаемые значения и выбрасывают предсказуемые ошибки при неправильных входных данных.
  4. Отсутствие утечек памяти: модули не удерживают излишние ссылки, тестировать профилировщиком при нагрузке.
  5. Документация: README или комментарии описывают API и примеры использования.

Примеры тестов (идеи):

  • unit: создать объект через фабрику и проверить поля name/type/priority;
  • интеграция: вызвать UIController.callChangeComponent() и убедиться, что текст в h1 изменился;
  • контракт: попытка вызвать несуществующий публичный метод должна давать понятную ошибку.

Совместимость и миграция

  • ES5 IIFE подойдёт для старых проектов и быстрого инкапсулирования без сборщика;
  • ES6-модули рекомендуются для современных приложений с bundler-ами (Webpack, Rollup) и для серверного Node.js с поддержкой ES-модулей;
  • при миграции от IIFE к ES6-модулям:
    • экспортируйте только публичный API;
    • проверьте точки импорта и удалите глобальные зависимости;
    • используйте named exports для тестируемости.

Пример замены IIFE на ES6-модуль (псевдо):

// uiController.js
let component = 'Replacement Text';
function changeComponent() {
  const element = document.querySelector('h1');
  element.textContent = component;
}
export function callChangeComponent() { changeComponent(); }

Частые ошибки и примеры отказов

  1. Открывать слишком много деталей в публичном API. Результат — сложность сопровождения.
  2. Дублировать логику создания объектов в разных местах вместо использования фабрики.
  3. Использовать фабрику как хранилище состояния (фабрика должна только создавать — состояние храните отдельно).
  4. Забивать модуль побочными эффектами при инициализации (например, манипуляциями DOM сразу при объявлении) — инициируйте модули явным вызовом.

Edge-case: если вам нужны разные реализации одного интерфейса в рантайме (плагины), лучше применить паттерн стратегия или абстрактную фабрику.

Мини-методология внедрения паттернов

  1. Опишите проблему: зачем нужна инкапсуляция или централизованное создание.
  2. Выберите паттерн: модуль для инкапсуляции, фабрика для создания объектов.
  3. Разбейте на шаги: реализовать базовую версию → покрыть тестами → профилировать → документировать.
  4. Реализуйте минимально жизнеспособную версию (MVP).
  5. Итерируйте: добавьте улучшения (регистры типов, DI, кеширование).

Короткий глоссарий (1‑строчка)

  • IIFE — немедленно вызываемая функция, использующаяся для создания локального скоупа.
  • ES6-модули — стандартный способ экспорта/импорта кода в современном JS.
  • Фабрика — функция/объект, создающий и возвращающий экземпляры нужных типов.
  • Инкапсуляция — сокрытие внутренней реализации за публичным интерфейсом.

Практические сниппеты и чеклист — шпаргалка

Шпаргалка по модулю (быстро):

  • Сделать IIFE или ES6-модуль.
  • Вернуть минимальный объект API.
  • Скрывaть состояние внутри.

Шпаргалка по фабрике (быстро):

  • Использовать map вместо цепочки if/else для большого числа типов.
  • Возвращать объекты с обычными методами, а не с побочными эффектами.
  • Добавить тесты на каждый тип.

Пример карты типов:

const typeMap = {
  urgent: (name) => ({ name, priority: 'as soon as possible' }),
  trivial: (name) => ({ name, priority: 'when you can' })
};

function createTask(name, type) {
  const creator = typeMap[type];
  if (!creator) throw new Error('Unknown type');
  const task = creator(name);
  task.type = type;
  task.define = function() { console.log(`${this.name} (${this.type}): ${this.priority}`)};
  return task;
}

Резюме

Выучив модульный паттерн и паттерн фабрики, вы получите основные инструменты для организации масштабируемого и сопровождаемого JavaScript-кода. Модуль хорош для инкапсуляции и создания стабильного API; фабрика — для централизованного создания схожих объектов. В современных проектах предпочтительнее комбинировать паттерны с ES6-модулями, функциями-фабриками и dependency injection.

Image Credit: Alltechbuzz/ Pixabay

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство