← Назад к вопросам
Как создать 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 (экспорт экземпляра) — самый простой и надёжный.