Singleton Pattern (GoF) — Синглтон
Гарантия одного экземпляра класса на процесс. Автор: «на JS почти бессмысленная вещь» — но знать обязательно для других языков и для понимания ES-модулей.
Проблема
В системе должен существовать ровно один объект с разделяемым состоянием: пул соединений к БД, конфигурация, логгер, event bus. Без паттерна каждый new создаёт отдельный экземпляр.
Где используется
- Database connection pool
- Конфигурация приложения (один объект settings)
- Логгер (один централизованный логгер)
- Event bus, store (Redux store — синглтон)
- Кэш приложения
- Модальные окна (один на страницу)
Решение
В JS — три уровня надёжности:
- Через свойство конструктора — простой, но взламывается извне (
Singleton.instance = null). - Через замыкание (IIFE) — instance в функциональном контексте, недоступен снаружи.
- Через ES-модуль —
export default new X. Модуль грузится один раз, экземпляр кэшируется.
Реализации
Через класс со статическим полем
class Singleton {
static _instance = null;
constructor(config) {
if (Singleton._instance) return Singleton._instance;
this.config = config;
Singleton._instance = this;
}
static getInstance(config) {
if (!Singleton._instance) {
Singleton._instance = new Singleton(config);
}
return Singleton._instance;
}
}
const a = new Singleton({ env: 'production' });
const b = new Singleton({ env: 'development' }); // игнорируется
a === b; // true
Через замыкание (IIFE)
const Singleton = (() => {
let instance;
return class {
constructor {
if (instance) return instance;
instance = this;
}
};
});
Через ES Module (рекомендуемый для JS)
// config.js — модуль сам по себе singleton
// Модуль выполняется один раз, все импорты получают тот же объект
class AppConfig {
constructor {
this.env = process.env.NODE_ENV || 'development';
this.apiUrl = process.env.API_URL || 'http://localhost:3000';
}
get(key) { return this[key]; }
}
export const config = new AppConfig();
// app.js, logger.js — везде один и тот же config
import { config } from './config.js';
Lazy Singleton — Pool
const DatabasePool = (() => {
let instance = null;
class Pool {
constructor(maxConnections) {
this.maxConnections = maxConnections;
this.connections = ;
}
query(sql) { /* ... */ }
}
return {
getInstance(max = 10) {
if (!instance) instance = new Pool(max);
return instance;
}
};
});
С возможностью reset (для тестов)
class Logger {
static _instance = null;
constructor(level = 'info') { this.level = level; this.logs = ; }
log(msg) { this.logs.push({ level: this.level, msg }); }
static getInstance(level) {
if (!Logger._instance) Logger._instance = new Logger(level);
return Logger._instance;
}
static reset { Logger._instance = null; } // только для тестов!
}
Где используется в JS-экосистеме
- Redux store — синглтон по умолчанию
- Database pool — один pool на приложение
- Конфигурация —
export const config = { ... } - Любой
export default new Xв Node.js
Подводные камни
- Часто антипаттерн: shared state, скрытые зависимости, усложняет тесты.
- Не разделяется между worker_threads / Web Workers — каждый поток создаёт свой.
- В тестах нужен
resetили DI вместо синглтона. - Контрол не полный в чистом JS: публичное
instance-свойство можно сбросить извне — нужно замыкание. - Не защищён от наследования в простой реализации:
class Base { constructor { if (Base.instance) return Base.instance; Base.instance = this; } } class Child extends Base {} new Base === new Child(); // true — но b ожидался Child! // Решение: проверять new.target - Порядок инициализации модулей: если singleton зависит от другого singleton, порядок import может вызвать проблемы.
- Multiton — расширение: ровно N инстансов, ключ → инстанс.
Главные тезисы автора
- «На JavaScript это почти бессмысленная вещь» — модули уже сами синглтоны.
- Часто относят к антипаттернам из-за shared state.
- «В JavaScript классно использовать систему модульности для создания синглтонов».
- Не безопасен без замыкания: публичное
instance-свойство можно сбросить извне. - Object Pool — расширение синглтона с ограничением (multiton + переиспользование).
🎓 Источники
- 🎓 Шаблон Singleton (синглтон) в JavaScript · 2018-12-10
- «На JS почти бессмысленная вещь»
- Реализация через свойство конструктора, через замыкание, через IIFE
- Уязвимость публичного instance
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- ES-модули как готовый синглтон
- Singleton часто антипаттерн (shared state)
- Multiton и Object Pool как варианты
- Refactoring Guru — Singleton
- Patterns.dev — Singleton