IntermedioPatrones de diseño

Singleton

El patrón que garantiza una única instancia global — cómo implementarlo bien y por qué casi siempre hay una alternativa mejor.

En esta página

Singleton es un patrón creacional que garantiza que una clase tenga una única instancia y ofrece un punto de acceso global a ella. Es el patrón más conocido — y también el más criticado, porque usado sin criterio introduce estado global encubierto.

La implementación

class Configuracion {
  private static instancia: Configuracion | null = null;

  private constructor(readonly valores: Record<string, string>) {}

  static getInstancia(): Configuracion {
    if (Configuracion.instancia === null) {
      Configuracion.instancia = new Configuracion(cargarConfig());
    }
    return Configuracion.instancia;
  }
}

const config = Configuracion.getInstancia();

Las tres piezas: constructor privado (nadie más puede instanciar), campo estático con la instancia, y un método getInstancia() con inicialización perezosa.

Servicio A Servicio B Servicio C Configuracióninstancia única

Un único punto de acceso global: todos comparten la misma instancia.

En lenguajes con hilos (Java, C#), la inicialización perezosa necesita sincronización — el clásico double-checked locking. En JavaScript el problema no existe por ser monohilo, y de hecho un módulo ES ya es un singleton: se evalúa una vez y se cachea.

// config.ts — singleton idiomático en JavaScript/TypeScript
export const config = cargarConfig();

Por qué tiene mala fama

  • Estado global disfrazado. Cualquier función puede leer y mutar la instancia sin declararlo en su firma. Los bugs se vuelven no locales: algo cambió la configuración “desde algún sitio”.
  • Rompe la testabilidad. El código que llama a Configuracion.getInstancia() dentro de sus funciones no puede recibir un doble de pruebas sin trucos (resetear estáticos entre tests, mocks de módulo).
  • Acopla al mecanismo, no al contrato. Los consumidores dependen de una clase concreta y de cómo se obtiene, no de una interfaz.

Cuándo sigue teniendo sentido

  • Recursos físicamente únicos y costosos: un pool de conexiones, un logger, la caché de proceso.
  • Cuando el ciclo de vida lo gestiona un contenedor de inyección de dependencias (NestJS, Spring): la clase se declara con scope singleton, pero los consumidores la reciben por constructor. Tienes la unicidad sin el acceso global — la mejor versión del patrón.

La alternativa por defecto

Antes de escribir getInstancia(), pregúntate: ¿puedo crear la instancia una vez en el arranque y pasarla como dependencia? Eso conserva la unicidad, hace las dependencias explícitas y deja los tests triviales.

El patrón complementario para entender esta crítica es la inyección de dependencias; y si te interesa la comunicación entre objetos sin acoplarlos, sigue con Observer.