CREATIONAL
Singleton
Garantire che una classe abbia una sola istanza e fornire un punto di accesso globale ad essa.
Esempi di Codice
❌ PROBLEMA: Senza Singleton - Istanze Multiple
Questo codice crea molteplici istanze della configurazione, causando inconsistenza e spreco di memoria.
JAVASCRIPT
// ❌ PROBLEMA: Ogni volta che creiamo AppConfig, abbiamo un'istanza diversa
class AppConfig {
constructor() {
this.settings = {
apiUrl: 'https://api.example.com',
timeout: 5000,
theme: 'dark'
};
// Caricamento pesante dal server (viene ripetuto ogni volta!)
this.loadFromServer();
}
loadFromServer() {
console.log('Loading config from server... (expensive operation)');
}
getSetting(key) {
return this.settings[key];
}
}
// ❌ Problema: Ogni modulo crea la propria istanza
const config1 = new AppConfig(); // Loading config from server...
const config2 = new AppConfig(); // Loading config from server... (di nuovo!)
config1.settings.theme = 'light';
console.log(config1.settings.theme); // 'light'
console.log(config2.settings.theme); // 'dark' - inconsistenza!
console.log(config1 === config2); // false - istanze diverse!✅ SOLUZIONE: Singleton - Una Sola Istanza
Il pattern Singleton garantisce che esista una sola istanza e fornisce un punto di accesso globale.
TYPESCRIPT
// ✅ SOLUZIONE: Singleton garantisce un'unica istanza
class AppConfig {
// Variabile statica privata per memorizzare l'istanza unica
private static instance: AppConfig;
private settings: Record<string, any>;
// Costruttore privato impedisce la creazione diretta con 'new'
private constructor() {
this.settings = {
apiUrl: 'https://api.example.com',
timeout: 5000,
theme: 'dark'
};
// Caricamento pesante avviene una sola volta
this.loadFromServer();
}
// Metodo statico per ottenere l'istanza unica
public static getInstance(): AppConfig {
// Lazy initialization: crea l'istanza solo se non esiste
if (!AppConfig.instance) {
AppConfig.instance = new AppConfig();
console.log('✅ New instance created');
} else {
console.log('♻️ Returning existing instance');
}
return AppConfig.instance;
}
private loadFromServer(): void {
console.log('Loading config from server... (only once!)');
}
public getSetting(key: string): any {
return this.settings[key];
}
public setSetting(key: string, value: any): void {
this.settings[key] = value;
}
}
// ✅ Utilizzo: Tutti ottengono la stessa istanza
const config1 = AppConfig.getInstance(); // ✅ New instance created
const config2 = AppConfig.getInstance(); // ♻️ Returning existing instance
config1.setSetting('theme', 'light');
console.log(config1.getSetting('theme')); // 'light'
console.log(config2.getSetting('theme')); // 'light' - consistenza!
console.log(config1 === config2); // true - stessa istanza!🎯 Esempio Pratico: Database Connection Pool
Caso d'uso reale: pool di connessioni al database condiviso in tutta l'app.
TYPESCRIPT
// 🎯 Esempio reale: Database Connection Pool
class DatabasePool {
private static instance: DatabasePool;
private connections: any[] = [];
private maxConnections: number = 10;
private currentConnections: number = 0;
private constructor() {
console.log('🔧 Initializing database connection pool...');
this.initializePool();
}
public static getInstance(): DatabasePool {
if (!DatabasePool.instance) {
DatabasePool.instance = new DatabasePool();
}
return DatabasePool.instance;
}
private initializePool(): void {
// Crea 5 connessioni iniziali
for (let i = 0; i < 5; i++) {
this.connections.push({
id: i,
inUse: false,
connection: `mongodb://localhost:27017/db_${i}`
});
}
this.currentConnections = 5;
}
// Ottieni una connessione dal pool
public getConnection(): any {
// Cerca una connessione libera
const available = this.connections.find(c => !c.inUse);
if (available) {
available.inUse = true;
console.log(`📤 Connection ${available.id} acquired`);
return available;
}
// Se non ci sono connessioni libere e non abbiamo raggiunto il max
if (this.currentConnections < this.maxConnections) {
const newConnection = {
id: this.currentConnections,
inUse: true,
connection: `mongodb://localhost:27017/db_${this.currentConnections}`
};
this.connections.push(newConnection);
this.currentConnections++;
console.log(`➕ New connection ${newConnection.id} created and acquired`);
return newConnection;
}
console.log('⚠️ No connections available, waiting...');
return null;
}
// Rilascia una connessione al pool
public releaseConnection(connection: any): void {
connection.inUse = false;
console.log(`📥 Connection ${connection.id} released`);
}
public getStats(): string {
const inUse = this.connections.filter(c => c.inUse).length;
return `Total: ${this.currentConnections}, In use: ${inUse}, Available: ${this.currentConnections - inUse}`;
}
}
// 🎯 Utilizzo in diversi moduli dell'applicazione
// Modulo User Service
function userService() {
const pool = DatabasePool.getInstance();
const conn = pool.getConnection();
// ... esegui query ...
pool.releaseConnection(conn);
}
// Modulo Order Service
function orderService() {
const pool = DatabasePool.getInstance(); // Stessa istanza!
const conn = pool.getConnection();
// ... esegui query ...
pool.releaseConnection(conn);
}
// Tutti i servizi condividono lo stesso pool
const pool1 = DatabasePool.getInstance();
const pool2 = DatabasePool.getInstance();
console.log(pool1 === pool2); // true
console.log(pool1.getStats());🆚 Confronto: Singleton vs Istanze Multiple
Visualizzazione delle differenze tra i due approcci.
TYPESCRIPT
// 🆚 CONFRONTO DIRETTO
// ❌ SENZA SINGLETON
class Logger {
private logs: string[] = [];
log(message: string) {
this.logs.push(`[${new Date().toISOString()}] ${message}`);
}
getLogs() {
return this.logs;
}
}
const logger1 = new Logger();
const logger2 = new Logger();
logger1.log('User logged in');
logger2.log('Order created');
console.log('Logger1 logs:', logger1.getLogs()); // ['User logged in']
console.log('Logger2 logs:', logger2.getLogs()); // ['Order created']
// ❌ I log sono divisi in istanze separate!
// ✅ CON SINGLETON
class SingletonLogger {
private static instance: SingletonLogger;
private logs: string[] = [];
private constructor() {}
static getInstance(): SingletonLogger {
if (!SingletonLogger.instance) {
SingletonLogger.instance = new SingletonLogger();
}
return SingletonLogger.instance;
}
log(message: string): void {
this.logs.push(`[${new Date().toISOString()}] ${message}`);
}
getLogs(): string[] {
return this.logs;
}
}
const sLogger1 = SingletonLogger.getInstance();
const sLogger2 = SingletonLogger.getInstance();
sLogger1.log('User logged in');
sLogger2.log('Order created');
console.log('Singleton logs:', sLogger1.getLogs());
// ['User logged in', 'Order created']
console.log('Are they the same?', sLogger1 === sLogger2); // true
// ✅ Tutti i log sono centralizzati in un'unica istanza!Esempi nel Mondo Reale
Database Connection Pool - Una sola istanza gestisce tutte le connessioni al database
Logger di sistema - Un solo logger centralizzato per tutta l'applicazione
Configuration Manager - Configurazione globale accessibile ovunque
Cache Manager - Una sola cache condivisa tra tutti i componenti
Thread Pool - Gestione centralizzata dei thread worker
Quando Usarlo
Quando deve esserci esattamente una sola istanza di una classe
Quando l'istanza deve essere accessibile da più parti del codice
Quando l'istanza unica dovrebbe essere estendibile tramite sottoclassi
Per gestire risorse condivise (database, file, configurazioni)
Quando NON Usarlo
Quando servono istanze multiple con stato diverso
Quando rende difficile il testing (dependency injection è meglio)
Quando crea accoppiamento stretto nel codice
Quando viola il Single Responsibility Principle
Pattern Correlati