Saltar al contenidoJMRG
Todas las entradas

Patrón Singleton: Una Instancia Global Controlada

design-patternscreationalsingleton

¿Qué es el Patrón Singleton?

El patrón Singleton garantiza que una clase tenga una única instancia y proporciona un punto de acceso global a ella. Es especialmente útil cuando necesitas controlar el acceso a recursos compartidos como conexiones de bases de datos, pools de conexiones o configuraciones de aplicación.

classDiagram
    class Singleton {
        -instance: Singleton$
        -Singleton()
        +getInstance(): Singleton$
        +operation()
    }

    note for Singleton "instance es estática\ngetInstance() es estático\nConstructor es privado"

Cuándo Usar Singleton

Usar Singleton:

  • Conexiones a bases de datos
  • Configuración de aplicación
  • Pools de conexiones
  • Loggers centralizados

Evitar Singleton:

  • En contextos con múltiples threads sin sincronización adecuada
  • Cuando dificulta las pruebas unitarias
  • Si introduce acoplamiento global innecesario

Implementación Paso a Paso

1. Constructor Privado

El constructor privado previene la instanciación externa directa con new:

class DatabaseConnection {
  private connectionString: string;

  private constructor(connectionString: string) {
    this.connectionString = connectionString;
  }
}

2. Propiedad Estática para la Instancia

La clase mantiene una referencia estática a su única instancia:

private static instance: DatabaseConnection | null = null;

3. Método de Acceso (getInstance)

Punto de acceso global que crea la instancia solo si no existe (Lazy Initialization):

public static getInstance(connectionString?: string): DatabaseConnection {
  if (!DatabaseConnection.instance) {
    DatabaseConnection.instance = new DatabaseConnection(
      connectionString ?? 'mongodb://localhost:27017/default'
    );
  }
  return DatabaseConnection.instance;
}

4. Método Reset para Testing

Permite resetear la instancia en pruebas unitarias:

public static resetInstance(): void {
  DatabaseConnection.instance = null;
}

Ejemplo Práctico Completo

interface User {
  id: string;
  name: string;
}

class DatabaseConnection {
  private static instance: DatabaseConnection | null = null;
  private connectionString: string;
  private connected: boolean = false;

  private constructor(connectionString: string) {
    this.connectionString = connectionString;
  }

  public static getInstance(connectionString?: string): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection(
        connectionString ?? 'mongodb://localhost:27017/default'
      );
    }
    return DatabaseConnection.instance;
  }

  public static resetInstance(): void {
    DatabaseConnection.instance = null;
  }

  async connect(): Promise<void> {
    if (this.connected) return;
    console.log(`Connecting to ${this.connectionString}...`);
    this.connected = true;
  }

  async query<T>(sql: string): Promise<T[]> {
    console.log(`Executing: ${sql}`);
    return [] as T[];
  }
}

// Uso
const db1 = DatabaseConnection.getInstance('mongodb://prod:27017/app');
const db2 = DatabaseConnection.getInstance();

console.log(db1 === db2); // true - misma instancia

await db1.connect();
const users = await db2.query<User>('SELECT * FROM users');

Variante: Singleton con Symbol

Para garantizar unicidad incluso entre diferentes módulos:

const SINGLETON_KEY = Symbol.for('app.singleton.config');

class ConfigurationManager {
  private config: Map<string, unknown> = new Map();

  static get instance(): ConfigurationManager {
    const globalAny = globalThis as Record<symbol, ConfigurationManager>;

    if (!globalAny[SINGLETON_KEY]) {
      globalAny[SINGLETON_KEY] = new ConfigurationManager();
    }

    return globalAny[SINGLETON_KEY];
  }

  set<T>(key: string, value: T): void {
    this.config.set(key, value);
  }

  get<T>(key: string): T | undefined {
    return this.config.get(key) as T;
  }
}

Resumen

Aspecto Descripción
Propósito Una única instancia con acceso global
Componentes Constructor privado, instancia estática, método getInstance
Beneficios Control de recursos, estado compartido consistente
Consideraciones Puede dificultar testing, usar resetInstance para pruebas

Conclusión

El patrón Singleton es fundamental para gestionar recursos compartidos de forma controlada. Úsalo con prudencia: es poderoso pero puede introducir acoplamiento global si se abusa de él.

En el próximo artículo exploraremos el Patrón Factory Method, que delega la creación de objetos a subclases especializadas.


Basado en "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).