В Node.js система модулей CommonJS по умолчанию предоставляет механизм для реализации паттерна Singleton благодаря особенностям кэширования модулей. Рассмотрим различные подходы к созданию Singleton.
Node.js кэширует модули после их первой загрузки. Это означает, что при повторных вызовах require()
возвращается тот же экземпляр объекта.
// dbConnection.js
class DBConnection {
constructor() {
this.connection = null;
console.log('DBConnection instance created');
}
connect() {
this.connection = 'Connected to database';
return this;
}
}
// Экспортируем сразу инстанс класса
module.exports = new DBConnection().connect();
При использовании в разных файлах:
// file1.js
const db1 = require('./dbConnection');
console.log(db1.connection); // 'Connected to database'
// file2.js
const db2 = require('./dbConnection');
console.log(db2.connection); // 'Connected to database' (тот же инстанс)
console.log(db1 === db2); // true
Если нужно больше контроля над созданием инстанса:
// singleton.js
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.value = Math.random();
Singleton.instance = this;
}
}
module.exports = Singleton;
Использование:
const Singleton = require('./singleton');
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
console.log(instance1.value === instance2.value); // true
Для отложенного создания инстанса:
// lazySingleton.js
class LazySingleton {
constructor() {
this.value = null;
}
init() {
if (!this.value) {
this.value = Math.random();
}
return this;
}
}
module.exports = new LazySingleton();
Использование:
const singleton = require('./lazySingleton');
// Инстанс создается только при вызове init()
console.log(singleton.value); // null
singleton.init();
console.log(singleton.value); // случайное число
С использованием синтаксиса приватных полей класса:
// modernSingleton.js
class ModernSingleton {
#value; // Приватное поле
constructor() {
if (ModernSingleton.#instance) {
return ModernSingleton.#instance;
}
this.#value = Math.random();
ModernSingleton.#instance = this;
}
static #instance = null;
get value() {
return this.#value;
}
}
module.exports = ModernSingleton;
Пример с подключением к базе данных:
// database.js
const { Client } = require('pg');
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.client = new Client({
connectionString: process.env.DATABASE_URL
});
this.isConnected = false;
Database.instance = this;
}
async connect() {
if (!this.isConnected) {
await this.client.connect();
this.isConnected = true;
}
return this;
}
}
module.exports = new Database();
В Node.js Singleton проще всего реализуется через механизм кэширования модулей. Экспорт инстанса класса вместо самого класса гарантирует, что все вызовы require()
будут возвращать один и тот же объект. Для более сложных сценариев можно использовать статические поля класса или механизм проверки существующего инстанса.