Singleton
Un patrón creacional para que una clase tenga una sola instancia, accesible desde cualquier parte del código.
Singleton es un patrón creacional que garantiza que una clase tenga una única instancia, y da un punto de acceso global a ella.
El problema
AndesShop necesita un InventoryLedger: el registro que lleva el conteo actual de stock de cada producto. Si distintas partes del sistema (el checkout, el panel de administración, el job que sincroniza con el depósito) crean cada una su propia instancia de este registro, cada instancia termina con su propia copia del conteo en memoria — y esas copias se desincronizan entre sí. Dos instancias distintas podrían decidir, cada una por su cuenta, que todavía queda una unidad disponible del mismo producto, y venderla las dos.
El problema no es solo evitar instancias duplicadas: es que además todo el sistema necesita encontrar la misma instancia sin que cada parte del código tenga que recibirla explícitamente como parámetro desde el punto de entrada de la aplicación.
La solución
Singleton hace privado el constructor de la clase, para que nadie pueda instanciarla directamente con new, y expone un método estático que crea la única instancia la primera vez que se la pide, y devuelve esa misma instancia en cualquier llamada posterior.
classDiagram
class InventoryLedger {
-instance InventoryLedger$
-stock Map~String, Integer~
-InventoryLedger()
+getInstance() InventoryLedger$
+reserve(String, int) boolean
}
class Checkout {
+confirm(Order) void
}
class WarehouseSyncJob {
+run() void
}
Checkout ..> InventoryLedger : getInstance()
WarehouseSyncJob ..> InventoryLedger : getInstance()
note for InventoryLedger "El subrayado ($) marca los miembros estáticos.
El constructor privado impide instanciarla con new."Ejemplo en Java
class InventoryLedger {
private static volatile InventoryLedger instance;
private final Map<String, Integer> stock = new ConcurrentHashMap<>();
// Constructor privado: nadie puede hacer "new InventoryLedger()" desde afuera
private InventoryLedger() {}
public static InventoryLedger getInstance() {
if (instance == null) {
synchronized (InventoryLedger.class) {
if (instance == null) {
instance = new InventoryLedger();
}
}
}
return instance;
}
public boolean reserve(String sku, int quantity) {
// compute() aplica la comprobación y el descuento como una sola operación
// atómica sobre esa clave. Es lo que hace segura la reserva concurrente.
Integer remaining = stock.compute(sku, (key, available) -> {
int current = available == null ? 0 : available;
return current < quantity ? current : current - quantity;
});
return remaining != null && remaining >= 0 && stock.get(sku) != null;
}
}
// Client code, desde cualquier parte del sistema: siempre la misma instancia
boolean reserved = InventoryLedger.getInstance().reserve("JKT-2024-BLUE", 1);
Cuándo usarlo
- Cuando necesitás que una clase tenga exactamente una instancia, accesible desde cualquier punto del código, típicamente para coordinar el acceso a un recurso compartido (una caché, un registro, un pool de conexiones).
Cuándo evitarlo
Singleton es, en la práctica, el patrón más discutido del catálogo — porque introduce estado global, algo que dificulta escribir tests (las instancias quedan compartidas entre tests salvo que se resetee explícitamente) y esconde una dependencia real detrás de una llamada estática en vez de recibirla explícitamente. Antes de usarlo, vale la pena evaluar si alcanza con crear una única instancia en el punto de entrada de la aplicación e inyectarla donde haga falta, sin forzar la restricción a nivel de la clase misma.
Dicho en los términos del Principio de Inversión de Dependencias: InventoryLedger.getInstance() es una dependencia que no aparece en la firma de nadie. Un Checkout que la invoca depende de una clase concreta sin declararlo, y no hay forma de sustituirla en un test sin tocar estado global. Recibir un InventoryLedger por constructor conserva “hay una sola instancia” —la crea el punto de entrada— y elimina las dos consecuencias caras.
Ventajas y desventajas
| Ventajas | Desventajas |
|---|---|
| Garantiza una única instancia y un punto de acceso conocido a ella | Introduce estado global, lo que puede acoplar código que debería ser independiente |
| Controla el acceso a un recurso compartido desde un solo lugar | Dificulta testear en aislamiento, salvo que se diseñe explícitamente para poder resetearse |
| Permite inicialización lazy, si la implementación la elige (no es inherente al patrón) | En entornos multihilo, una implementación mal hecha puede crear más de una instancia |
Relación con otros patrones
- Abstract Factory, Builder y Prototype pueden implementarse como Singleton cuando alcanza con una única fábrica/builder compartido.
- Facade suele implementarse como Singleton, aunque no es un requisito del patrón.
