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.
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
| Variante | Caso de uso | Compromiso |
|---|---|---|
| Inicialización perezosa | Instancia creada en el primer acceso | Problemas de thread-safety |
| Inicialización eager | Instancia creada al cargar la clase | Sin problemas de hilos, puede desperdiciar recursos |
| Doble verificación de bloqueo | Inicialización perezosa de alto rendimiento | Má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:
| Problema | Solucion |
|---|---|
| Dificil de testear | Inyeccion de dependencias |
| Estado global | Usar DI container en su lugar |
| No thread-safe (Java) | Double-checked locking |
| No funciona con clustering | Una instancia por proceso |
| Acoplamiento oculto | Pasar como parametro |
Alternativas al Singleton:
| Alternativa | Ventaja |
|---|---|
| DI container | Testable, explicito |
| Module pattern | Una instancia por modulo (Node.js) |
| Factory + cache | Control sobre creacion |
| Monostate | Mismo 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. Recursos Relacionados
Parse JSON
How to parse JSON strings into native data structures across multiple programming languages.
PatternFactory Pattern
Create objects without specifying the exact class to instantiate. A creational design pattern for flexible object creation.
GuideREST API Design Guide
A thorough guide to designing clean, scalable, and maintainable REST APIs.
PatternAbstract Factory Pattern
Create families of related objects without specifying concrete classes. A creational design pattern for consistent object families.
PatternBuilder Pattern
Construct complex objects step by step. A creational design pattern for readable, configurable object construction.
PatternCache-Aside Pattern
Load data into the cache on demand from the backing store. A caching pattern that gives the application full control over what and when to cache.
PatternDependency Injection Pattern
Supply dependencies from outside rather than creating them internally. An architectural pattern for decoupled, testable code.