Skip to content
StackPractices
beginner Por Mathias Paulenko

Patrón Singleton

Garantiza que una clase tenga una única instancia y proporciona un acceso global a ella. Patrón de diseño creacional para controlar la creación de objetos.

Temas: design

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Patrón Singleton

Visión general

El Patrón Singleton es un patrón de diseño creacional que restringe una clase a una única instancia y proporciona un punto de acceso global a ella. Es útil cuando se necesita exactamente un objeto para coordinar acciones en todo el sistema.

Casos de uso comunes incluyen pools de conexiones a bases de datos, gestores de configuración y servicios de logging.

Cuándo usarlo

Usa el Patrón Singleton cuando:

  • Debe existir exactamente una instancia de una clase en el sistema
  • Se necesita acceso controlado a un recurso compartido (ej. configuración, caché, pool de conexiones)
  • Necesitas un punto de acceso global sin contaminar el espacio de nombres con variables globales
  • Se desea inicialización perezosa para evitar crear la instancia hasta que se necesite

Solución

Python

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

# Uso
a = Singleton()
b = Singleton()
print(a is b)  # True

JavaScript

class Singleton {
  static #instance = null;

  static getInstance() {
    if (!Singleton.#instance) {
      Singleton.#instance = new Singleton();
    }
    return Singleton.#instance;
  }
}

// Uso
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true

Java

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// Uso
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
System.out.println(a == b); // true

Explicación

El Patrón Singleton garantiza una única instancia a través de tres mecanismos:

  • Constructor privado: Evita la instanciación directa desde fuera de la clase
  • Campo de instancia estático: Almacena la única instancia compartida
  • Método de acceso global: Proporciona una forma controlada de recuperar la instancia

En entornos multi-hilo (como Java), usa synchronized o inicialización eager para prevenir condiciones de carrera durante la creación de la instancia.

Variantes

VarianteCaso de usoCompromiso
Inicialización perezosaInstancia creada en el primer accesoProblemas de thread-safety
Inicialización eagerInstancia creada al cargar la claseSin problemas de hilos, puede desperdiciar recursos
Doble verificación de bloqueoInicialización perezosa de alto rendimientoMás complejo, propenso a errores en algunos lenguajes

Lo que funciona

  • Haz el constructor privado para prevenir la instanciación directa accidental
  • Usa inicialización perezosa solo cuando el costo de inicio importa
  • Considera la seguridad de hilos en entornos concurrentes
  • Evita el abuso: los Singletons pueden dificultar las pruebas unitarias debido al estado global oculto
  • Documenta la naturaleza singleton para que otros desarrolladores no intenten crear múltiples instancias

Errores comunes

  • Condiciones de carrera: dos hilos crean instancias separadas simultáneamente
  • Dificultades de testing: el estado global oculto hace que las pruebas dependan del orden
  • Abuso: convertir cada servicio compartido en singleton aumenta el acoplamiento
  • Problemas de serialización: deserializar puede crear instancias duplicadas a menos que se gestione
  • Uso incorrecto de herencia: las subclases pueden romper la garantía de instancia única

Ejemplos del mundo real

Pool de conexiones a base de datos

La mayoría de drivers de base de datos (SQLAlchemy, JDBC connection pools) usan un patrón similar a singleton para gestionar un pool fijo de conexiones. Crear una nueva conexión por cada query agotaría el servidor de base de datos.

Gestor de configuración

Las aplicaciones cargan configuración desde archivos o variables de entorno una sola vez al iniciar. Un gestor de configuración singleton asegura que todos los módulos lean del mismo estado en memoria sin recargar desde disco.

Capa de caché

Las cachés en memoria (clientes Redis, cachés LRU locales) se comparten típicamente a través de la aplicación. Un singleton garantiza consistencia de caché y evita duplicación de memoria.

Fábrica de loggers

Los frameworks de logging usan frecuentemente un registro de loggers nombrados que se comportan como singletons. Llamar Logger.getLogger("my.module") múltiples veces devuelve la misma instancia.

Preguntas frecuentes

P: ¿Es Singleton un anti-patrón? R: No inherentemente, pero su abuso lleva a acoplamiento fuerte y dependencias ocultas. Úsalo con moderación para recursos que realmente requieren una única instancia.

P: ¿Cómo hago un Singleton thread-safe en Python? R: El enfoque __new__ mostrado arriba es thread-safe en CPython debido al GIL. Para mayor seguridad, usa un lock o variables a nivel de módulo.

P: ¿Puede un Singleton tener subclases? R: Es posible pero complicado. Cada subclase puede terminar con su propia instancia, lo cual puede o no ser el comportamiento deseado.

P: ¿Cómo testeo unitariamente código que usa un Singleton? R: Inyecta el singleton como dependencia en lugar de llamarlo directamente, o proporciona un método reset() para tests. Alternativamente, usa una fábrica que devuelva el singleton por defecto pero pueda ser mockeada en tests.

P: ¿Cuáles son alternativas al Singleton? R: Inyección de dependencias, localizadores de servicios, o variables a nivel de módulo en lenguajes que lo soporten (los módulos de Python son singletons naturales). Estos enfoques hacen las dependencias explícitas y más fáciles de testear.

¿Es este patrón adecuado para proyectos pequeños?

Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.

¿Cómo se compara este patrón con alternativas?

Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.

¿Puedo aplicar este patrón parcialmente?

Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.

Temas Avanzados

Escenario: Singleton para Conexion de Base de Datos

// Singleton con inicializacion diferida (lazy)
class DatabaseConnection {
  private static instance: DatabaseConnection | null = null;
  private pool: ConnectionPool;

  private constructor(config: DBConfig) {
    this.pool = createPool({
      host: config.host,
      port: config.port,
      max: config.maxConnections,  // 20
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    });
  }

  static getInstance(config?: DBConfig): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      if (!config) throw new Error("Config required for first init");
      DatabaseConnection.instance = new DatabaseConnection(config);
    }
    return DatabaseConnection.instance;
  }

  async query(sql: string, params: unknown[]): Promise<ResultSet> {
    return this.pool.query(sql, params);
  }

  async close(): Promise<void> {
    await this.pool.end();
    DatabaseConnection.instance = null;
  }
}

// Uso
const db = DatabaseConnection.getInstance({
  host: "localhost",
  port: 5432,
  maxConnections: 20,
});

// En tests: resetear entre suites
afterAll(async () => {
  await DatabaseConnection.getInstance().close();
});

Problemas del Singleton y soluciones:

ProblemaSolucion
Dificil de testearInyeccion de dependencias
Estado globalUsar DI container en su lugar
No thread-safe (Java)Double-checked locking
No funciona con clusteringUna instancia por proceso
Acoplamiento ocultoPasar como parametro

Alternativas al Singleton:

AlternativaVentaja
DI containerTestable, explicito
Module patternUna instancia por modulo (Node.js)
Factory + cacheControl sobre creacion
MonostateMismo estado, multiples instancias

Lecciones:

  • Singleton es util para recursos caros (DB, cache, logger)
  • En Node.js, module caching es singleton implicito
  • En tests, siempre provee forma de resetear la instancia
  • Prefiere DI container sobre Singleton para testabilidad
  • Nunca uses Singleton para logica de negocio

### Cuando NO debo usar Singleton?

No uses Singleton cuando necesitas multiples instancias (ej: conexiones a diferentes DBs), cuando hace el codigo dificil de testear (usa DI), o cuando el estado global causa bugs dificiles de rastrear. Para logica de negocio, usa DI container. Para configuracion, usa module pattern. Singleton es apropiado solo para recursos compartidos caros: DB pool, cache, logger.




























End of document. Review and update quarterly.