Factory Pattern — Фабрика

Функция или объект, который создаёт другие объекты. Не из канонического GoF (это Factory Method), но в JS — повсеместный приём.

Проблема

Создание объекта со сложной инициализацией не должно повторяться в коде клиента. Конструктор класса часто недостаточен: нужна доинициализация, выбор подкласса, регистрация в коллекции.

Где используется

  • Создание UI-компонентов по типу
  • Парсинг данных (JSON → разные классы)
  • Создание соединений (HTTP, WebSocket)
  • Игровые объекты (враги, предметы)
  • React.createElement — фабрика элементов

Решение

Фабрика — это более высокоуровневая абстракция, чем класс. Лестница абстракций по автору: instance → class → factory → pool.

  • Объект собирается из «трэша»: литералов объектов, примесей (mixins), оберток (wrappers).
  • Может быть функцией, классом, методом класса (Factory Method).
  • Может доинициализировать, добавить в коллекцию, провалидировать.

Реализации

Factory Function (фабричная функция)

// Простейшая фабрика — функция, возвращающая объект
const createUser = (name, role) => ({
  name,
  role,
  createdAt: new Date,
  greet { return `Hi, ${this.name} (${this.role})`; }
});

// V8 создаст hidden class для всех users одинаковой формы
const u1 = createUser('Marcus', 'admin');
const u2 = createUser('Mark', 'user');
// Скобки — чтобы стрелка не приняла {} за блок
const make = (name, group) => ({ name, group });
const wrong = (name, group) => { name, group };  // вернёт undefined

Factory Function с приватностью через замыкание

function createCounter(initial = 0) {
  let count = initial; // приватно

  return {
    increment { return ++count; },
    decrement { return --count; },
    getCount { return count; },
    reset { count = initial; }
  };
}

const counter = createCounter(10);
counter.increment;           // 11
counter.count;                 // undefined — приватно
counter.getCount;            // 11

Factory Method (фабричный метод)

class Notification {
  constructor(message) { this.message = message; }
  show { throw new Error('abstract'); }
}

class EmailNotification extends Notification {
  show { console.log(`Email: ${this.message}`); }
}
class SMSNotification extends Notification {
  show { console.log(`SMS: ${this.message}`); }
}

class NotificationFactory {
  static create(type, message) {
    switch (type) {
      case 'email': return new EmailNotification(message);
      case 'sms':   return new SMSNotification(message);
      default: throw new Error(`Unknown type: ${type}`);
    }
  }
}

Фабрика с реестром (предпочтительнее switch)

class ShapeFactory {
  static registry = new Map();

  static register(type, creator) { this.registry.set(type, creator); }

  static create(type, ...args) {
    const creator = this.registry.get(type);
    if (!creator) throw new Error(`Type "${type}" not registered`);
    return creator(...args);
  }
}

ShapeFactory.register('circle', (r) => ({ type: 'circle', area:  => Math.PI * r * r }));
ShapeFactory.register('rect', (w, h) => ({ type: 'rect', area:  => w * h }));

// Можно добавлять новые типы без изменения фабрики (OCP)
ShapeFactory.register('triangle', (b, h) => ({ type: 'triangle', area:  => 0.5 * b * h }));

API Response Parser

function createApiResponse({ status, data, error }) {
  if (status >= 200 && status < 300) {
    return {
      ok: true, status, data,
      getData { return this.data; }
    };
  }
  return {
    ok: false, status, error: error || 'Unknown',
    getData { throw new Error(this.error); }
  };
}

Где используется в JS-экосистеме

  • Node.js: Buffer.from, Array.from, Object.create — фабрики
  • DOM: document.createElement(tag) — Factory Method
  • React: компоненты как фабрики React-элементов
  • Знаковая factorify — обобщённая фабрика из лекций автора

Подводные камни

  • В JS объект-литерал в стрелке нужно оборачивать в `` — иначе будет блок кода.
  • Фабрика теряет instanceof-проверку — гибкость минус полиморфизм.
  • V8 группирует одноформенные объекты в hidden class — фабрика без класса не теряет производительности.
  • Фабрика когда достаточно конструктора — избыточно: createPoint(x, y) => new Point(x, y).
  • Огромный switch нарушает OCP — используй реестр.

Главные тезисы автора

  • В JS «часто объекты не из классов создают, а собираются из всякого трэша» — литералов, примесей, оберток.
  • factorify — обобщённая фабрика, которая превращает любой класс в фабрику.
  • Фабрика стоит выше класса в иерархии абстракций, но ниже пула.

🎓 Источники

См. также