← Назад к вопросам

Как создать Singleton с помощью модульной системы Node.js?

1.8 Middle🔥 131 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как создать Singleton с помощью модульной системы Node.js?

Singleton — паттерн проектирования, гарантирующий единственный экземпляр класса. В Node.js это реализуется очень просто благодаря встроенному механизму кэширования модулей.

Основной механизм

Каждый модуль Node.js кэшируется после первого импорта. Все последующие импорты одного модуля возвращают ссылку на один и тот же объект.

Способ 1: Экспорт готового экземпляра (самый простой)

// logger.js
class Logger {
  constructor() {
    console.log('Logger created');
    this.logs = [];
  }

  log(message) {
    this.logs.push(message);
    console.log(message);
  }
}

// Экспортируем только один экземпляр
module.exports = new Logger();

Использование:

const logger1 = require('./logger');
const logger2 = require('./logger');

console.log(logger1 === logger2); // true

Способ 2: Проверка в конструкторе

class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    this.connected = false;
    Database.instance = this;
  }

  connect() {
    this.connected = true;
  }
}

module.exports = new Database();

Способ 3: Статический метод getInstance()

class Config {
  static instance = null;

  constructor() {
    if (Config.instance) return Config.instance;
    this.settings = {};
    Config.instance = this;
  }

  static getInstance() {
    if (!Config.instance) {
      Config.instance = new Config();
    }
    return Config.instance;
  }
}

module.exports = Config;

Использование:

const Config = require('./config');
const config = Config.getInstance();

Практический пример: Database Connection Pool

class DbPool {
  constructor() {
    if (DbPool.instance) return DbPool.instance;
    this.connections = [];
    this.maxConnections = 5;
    DbPool.instance = this;
    console.log('Pool initialized');
  }

  getConnection() {
    if (this.connections.length < this.maxConnections) {
      const conn = Math.random();
      this.connections.push(conn);
      return conn;
    }
    throw new Error('No available connections');
  }

  release(conn) {
    this.connections = this.connections.filter(c => c !== conn);
  }
}

module.exports = new DbPool();

Проблема и решение: Circular Dependencies

Если модули зависят друг от друга:

// logger.js
const config = require('./config');

class Logger {
  log(msg) {
    console.log(`[${config.appName}] ${msg}`);
  }
}

module.exports = new Logger();
// config.js
const logger = require('./logger');

Решение: Ленивая инициализация

// logger.js
let config;

class Logger {
  log(msg) {
    if (!config) config = require('./config');
    console.log(`[${config.appName}] ${msg}`);
  }
}

module.exports = new Logger();

Тестирование Singleton

test('returns same instance', () => {
  delete require.cache[require.resolve('./logger')];
  const logger1 = require('./logger');
  const logger2 = require('./logger');
  expect(logger1).toBe(logger2);
});

test('preserves state', () => {
  delete require.cache[require.resolve('./logger')];
  const logger = require('./logger');
  logger.log('test');
  expect(logger.logs).toContain('test');
});

Плюсы и минусы

Плюсы:

  • Просто реализуется
  • Гарантирует единственность
  • Идеален для логгеров и конфигов

Минусы:

  • Глобальное состояние может затруднить тестирование
  • Скрывает зависимости
  • require.cache нужно очищать в тестах

Рекомендация

Для production используй способ 1 (экспорт экземпляра) — самый простой и надёжный.