Explora el patrón Singleton: cuándo usarlo, ejemplos prácticos en TypeScript y alternativas esenciales.
January 19, 2026 (3mo ago)
Dominar el patrón Singleton: Una guía completa para desarrolladores
Explora el patrón Singleton: cuándo usarlo, ejemplos prácticos en TypeScript y alternativas esenciales.
← Back to blog
Dominar el patrón Singleton en TypeScript: Una guía completa

En el mundo del desarrollo de software, algunas herramientas son poderosas pero deben manejarse con cuidado. El patrón Singleton es una de ellas. En su esencia, es un concepto simple: garantizar que una clase tenga solo una instancia y proporcionar una única forma global de acceder a ella.1
Piénsalo como un gestor de configuración central o un servicio de registro (logging) dedicado para toda tu aplicación. No querrías que hubiera múltiples objetos de configuración conflictivos flotando por ahí, ni que las entradas de registro se dispersaran en diferentes archivos por instancias de logger en competencia. El Singleton aporta orden al prevenir estos duplicados, ahorrando memoria y evitando el caos. Es un patrón fundamental para gestionar el acceso a recursos compartidos.
¿Qué es el patrón Singleton y cuándo es útil?
Imagínate un reino medieval con un único Escritor Real oficial. Esta persona es la única autorizada para registrar los decretos reales. Esto asegura que cada ley y anuncio sea coherente, esté debidamente autorizado y almacenado en un único registro definitivo. Si cualquiera pudiera decidir ser escriba, el reino pronto caería en el caos con registros contradictorios y confusión general.
El patrón de diseño Singleton opera con este mismo principio dentro de tu software. Su trabajo principal es restringir una clase para que solo se pueda crear un único objeto a partir de ella. Esta única instancia se convierte en la fuente de la verdad para una tarea específica, y es fácilmente accesible desde cualquier parte de tu base de código. Así es como se impone control sobre recursos que nunca deben duplicarse.
Propósito central y analogía
El Singleton no se trata solo de impedir que se creen nuevos objetos; se trata de centralizar el control. Al igual que el Escritor Real proporciona un único punto de acceso a los registros oficiales del reino, una instancia Singleton ofrece una puerta de entrada universal a un recurso compartido. Impide que diferentes partes de tu aplicación creen sus propias versiones aisladas y potencialmente conflictivas.
Un pool de conexiones a base de datos es un ejemplo clásico. Definitivamente no quieres que cada componente de tu aplicación abra su propia conexión separada a la base de datos; esa es una forma segura de agotar los recursos del servidor y paralizar el rendimiento. En su lugar, un Singleton puede gestionar un único pool de conexiones, entregándolas de forma eficiente según sea necesario.
La idea central es simple pero poderosa: una clase, una instancia, un punto de acceso global. Esta estructura garantiza que todas las interacciones con un recurso específico pasen por un único canal controlado.
Patrón Singleton de un vistazo
| Característica | Descripción y justificación |
|---|---|
| Instancia única | La clase está diseñada para tener solo una instancia durante el ciclo de vida de la aplicación, a menudo aplicada con un constructor privado. |
| Punto de acceso global | Un método estático (p. ej., getInstance()) proporciona una forma única y conocida de acceder a la instancia desde cualquier parte del código. |
| Inicialización perezosa | La instancia única a menudo se crea la primera vez que se solicita, no al arrancar la aplicación, lo que puede mejorar el rendimiento. |
| Gestión de estado | Actúa como un lugar centralizado para una pieza específica de estado global, como configuraciones de la aplicación o la sesión de un usuario. |
Esta tabla resume claramente por qué existe el patrón: para imponer una instancia única y accesible globalmente para recursos que son inherentemente singulares.
Casos de uso prácticos
Aunque el patrón Singleton tiene sus críticos, no está exento de usos legítimos. Es más eficaz cuando tienes un recurso que, por su propia naturaleza, es único dentro del sistema.
Aquí hay algunos escenarios donde un Singleton tiene sentido:
- Servicios de registro (logging): una única instancia de logger asegura que todos los eventos confluyan en el mismo archivo o flujo.
- Gestión de configuración: una única fuente para los ajustes de la aplicación evita inconsistencias entre módulos.
- Acceso a interfaces de hardware: una única interfaz a un dispositivo previene comandos en conflicto.
Los beneficios y desventajas de usar Singletons

El patrón Singleton puede sentirse como una herramienta confiable cuando necesitas un único punto de acceso a un recurso compartido. Te ofrece una forma directa de gestionar cosas como un objeto de configuración o un servicio de logging en toda tu aplicación.
Beneficios de los Singletons
- El punto de acceso global simplifica el uso entre módulos.
- La conservación de recursos mediante inicialización perezosa puede reducir el coste de arranque.
- La reducción de duplicaciones evita múltiples instancias conflictivas de recursos costosos.
Desventajas de los Singletons
- Acoplamiento fuerte: las clases pueden ocultar dependencias al acceder al estado global.
- Estado global: el estado mutable compartido puede causar errores difíciles de encontrar.
- Efectos secundarios ocultos: los métodos que dependen de un Singleton no muestran esa dependencia en sus firmas, complicando el razonamiento y las pruebas.
Impacto en las pruebas y el acoplamiento
Los Singletons complican las pruebas unitarias porque introducen estado global y persistente. Las pruebas corren el riesgo de filtrar estado entre ejecuciones, y el mocking de un Singleton puede volverse incómodo. Los equipos modernos a menudo prefieren la inyección de dependencias porque hace que las dependencias sean explícitas y fáciles de reemplazar durante las pruebas.3
Equilibrando los compromisos
Al decidir si usar un Singleton, evalúa la conveniencia frente a la mantenibilidad y la capacidad de prueba a largo plazo. Para bases de código legado, las refactorizaciones incrementales hacia la inyección de dependencias suelen ser la ruta más segura: mantener el comportamiento mientras reduces el acoplamiento oculto y mejoras la testabilidad.
Los Singletons cambian simplicidad por estado global, así que elige con sabiduría según las necesidades de tu equipo.
Conclusiones clave
- Usa Singletons con moderación y solo para servicios que realmente deben ser únicos.
- Prefiere la inyección de dependencias explícita para un mejor desacoplamiento y testabilidad.
- Si debes usar un Singleton, hazlo perezoso y ten en cuenta la concurrencia y la seguridad de hilos.
- Para sistemas legacy, elimina los Singletons de forma incremental introduciendo interfaces y DI en la raíz de composición.
Cómo implementar el patrón Singleton en TypeScript

Pasemos de lo abstracto a lo práctico y construyamos un Singleton moderno y con tipado en TypeScript. La salsa secreta es un constructor private y un método static que actúa como portero. Esta combinación asegura que ninguna otra parte de tu aplicación pueda crear una nueva instancia; todos pasan por el único punto de entrada.2
Para nuestro ejemplo práctico, crearemos un ConfigManager. Esta clase carga y sirve los ajustes de la aplicación, garantizando que cada componente lea de la misma fuente de verdad.
Construyendo un ConfigManager con tipos seguros
// A practical example of the Singleton pattern for configuration management.
class ConfigManager {
// 1. A private, static property to hold the single instance.
private static instance: ConfigManager;
// 2. A place to store our configuration data.
private settings: Map<string, any> = new Map();
// 3. The private constructor. This stops `new ConfigManager()` from working anywhere else.
private constructor() {
// In a real app, you'd load from a file, environment variables, or a service.
console.log("Initializing ConfigManager instance...");
this.settings.set("API_URL", "https://api.example.com");
this.settings.set("TIMEOUT", 5000);
}
// 4. The public, static method that controls access to the single instance.
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
// 5. A regular public method to get a specific setting.
public get(key: string): any {
return this.settings.get(key);
}
}
// TypeScript will stop you from doing this:
// const config = new ConfigManager(); // Error: Constructor of class 'ConfigManager' is private.
Esta estructura usa los modificadores de acceso de TypeScript para aplicar una clase de instancia única e inicialización perezosa. El constructor private y el patrón de instancia static son directos y efectivos para muchas necesidades simples.
Poniendo el Singleton a trabajar en un servicio
class ApiService {
private apiUrl: string;
constructor() {
const config = ConfigManager.getInstance();
this.apiUrl = config.get("API_URL");
console.log(`ApiService initialized with API URL: ${this.apiUrl}`);
}
public fetchData(): void {
console.log(`Fetching data from ${this.apiUrl}...`);
// Real data-fetching logic would live here.
}
}
// --- Application Entry Point ---
console.log("Application starting...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application finished.");
Cuando ejecutes este código, deberías ver el mensaje de inicialización de ConfigManager solo una vez, demostrando que ambos servicios recibieron la misma instancia.
Por qué los Singletons tienen tan mala fama
El patrón Singleton resulta atractivo porque es simple y te da un objeto accesible globalmente. Lo que puede salir mal es el acoplamiento oculto, el estado global mutable y las pesadillas para las pruebas. Cuando una clase accede silenciosamente a una instancia global, oculta una dependencia que debería ser explícita en su constructor. Esto hace que el sistema sea más difícil de razonar y de probar.3
Pesadillas en concurrencia y estado global
Los Singletons con estado pueden causar condiciones de carrera en escenarios concurrentes. Considera un SessionCounter donde dos peticiones simultáneas incrementan el mismo contador. Sin sincronización, ambas pueden leer el mismo valor inicial y escribir actualizaciones en conflicto. Estos errores dependen del tiempo y son difíciles de reproducir.
El dilema de las pruebas
Los Singletons hacen que las pruebas unitarias sean frágiles porque el estado puede filtrarse entre pruebas y el mocking se vuelve difícil. Las pruebas pueden empezar a depender del orden de ejecución y el conjunto se vuelve inestable. Por eso los equipos suelen adoptar la inyección de dependencias: hace que las dependencias sean explícitas y fáciles de simular.
A pesar de estos problemas, los Singletons persisten en la naturaleza. A menudo se propagan por una base de código una vez introducidos, por lo que auditar e introducir refactorizaciones incrementales es importante al mejorar la arquitectura.
Alternativas modernas al patrón Singleton
Después de ver los riesgos que introducen los Singletons, probablemente te preguntes: "¿Qué debería usar en su lugar?" La inyección de dependencias (DI) es el enfoque preferido para gestionar recursos compartidos. DI hace las dependencias explícitas y mejora la testabilidad y la modularidad.
Inyección de dependencias frente a Singletons
Compara el ApiService original, que accede a un Singleton, con una versión que acepta un gestor de configuración vía su constructor.
interface IConfigManager {
get(key: string): any;
}
class ApiService {
private apiUrl: string;
constructor(config: IConfigManager) {
this.apiUrl = config.get("API_URL");
}
}
Ahora ApiService depende únicamente del contrato IConfigManager. Durante las pruebas puedes pasar un falso o mock, haciendo las pruebas rápidas y predecibles.
Al invertir el control sobre quién crea las dependencias, los componentes se vuelven más centrados y flexibles. Esta idea es central en el Principio de Inversión de Dependencias.
El papel de los contenedores IoC
Un contenedor de Inversión de Control (IoC) gestiona la creación de objetos e inyección para tu aplicación. Frameworks populares de TypeScript proporcionan contenedores DI integrados, como NestJS y Angular, o bibliotecas como InversifyJS para proyectos generales.45
Los contenedores te permiten elegir cómo se comparten los objetos: ciclos de vida transitorios, por alcance o tipo singleton. Esto te da los beneficios de una instancia compartida única sin el acoplamiento oculto de un Singleton programático.
Cómo refactorizar Singletons en una base de código legacy
Trabaja de forma incremental. Identifica dónde se usa el Singleton, define una interfaz clara que describa su comportamiento y comienza a cambiar consumidores individuales para aceptar la interfaz mediante inyección por constructor. Luego conecta la instancia concreta en la raíz de composición o deja que un contenedor DI la gestione.
Paso 1: Identificar y aislar el Singleton
Encuentra cada llamada a MySingleton.getInstance() y dibuja un límite alrededor de las responsabilidades del Singleton. Define una interfaz que liste los métodos públicos que necesitas.
Paso 2: Introducir la inyección de dependencias de forma incremental
Refactoriza un consumidor a la vez:
- Cambia el constructor para aceptar la interfaz.
- Reemplaza las llamadas directas a
getInstance()por llamadas a la instancia inyectada. - En el punto de instanciación, pasa la instancia Singleton hasta que la migración completa esté terminada.
Esto mantiene la aplicación estable mientras reduces el acoplamiento oculto.
Paso 3: Reemplazar el Singleton por una instancia gestionada
Una vez que los consumidores acepten dependencias vía la interfaz, puedes eliminar el getInstance() estático y convertir la implementación en una clase normal con constructor público. Crea una instancia en la raíz de composición y pásala donde sea necesario, o deja que un contenedor DI gestione el ciclo de vida y el alcance.
Respondiendo a tus preguntas candentes sobre Singletons
¿Son los Singletons siempre una mala idea?
No siempre. Pueden tener sentido para servicios verdaderamente únicos y sin estado, como un logger centralizado o un adaptador de hardware. Incluso entonces, DI y una raíz de composición controlada a menudo ofrecen el mismo comportamiento con mejor testabilidad.
¿Cómo estropean los Singletons las pruebas unitarias?
Introducen estado global y persistente que puede filtrarse entre pruebas y dificultar el mocking. Las pruebas pueden volverse dependientes del orden y frágiles. DI simplifica las pruebas porque los mocks pueden inyectarse directamente.
¿No es una clase estática básicamente lo mismo?
No. Una clase estática solo aloja miembros estáticos y no puede instanciarse. Un Singleton tiene una instancia real y puede implementar interfaces y pasarse como objeto. Ambos enfoques pueden llevar a un acoplamiento fuerte, por lo que prefieres DI para mayor flexibilidad.
Próximos pasos para tu equipo
Inicia una conversación sobre dónde, si en algún lugar, realmente se requiere una instancia compartida única. Prototipa DI en un módulo aislado, adopta estándares de codificación claros y usa herramientas para medir la deuda técnica. La programación en pareja y la retroalimentación continua ayudan a mantener las refactorizaciones seguras y eficaces.
Recuerda, ningún patrón de diseño es una bala de plata. Los Singletons tienen su lugar, pero deben usarse con criterio y acompañados de interfaces limpias y reglas claras de propiedad.
FAQ — Preguntas rápidas
P: ¿Cuándo es apropiado un Singleton? A: Cuando un recurso es realmente único y sin estado, como un logger centralizado o un adaptador de hardware. Prefiere DI siempre que sea posible.
P: ¿Cómo puedo probar código que usa un Singleton ahora? A: Introduce una interfaz, refactoriza los consumidores para aceptar la interfaz e inyecta un doble de prueba. Haz esto de forma incremental para evitar regresiones a gran escala.
P: ¿Cuál es el camino más seguro para eliminar Singletons de una app legacy? A: Mapea los usos, define interfaces, refactoriza los consumidores para aceptar dependencias y luego crea e inyecta una única instancia en la raíz de composición o usa un contenedor DI.
La IA escribe código.Tú lo haces durar.
En la era de la aceleración de la IA, el código limpio no es solo una buena práctica — es la diferencia entre sistemas que escalan y bases de código que colapsan bajo su propio peso.