using System;
using System.Collections.Generic;
namespace CafeteriaSingleton
{
public class Pedido
{
public string Cliente { get; set; }
public string Bebida { get; set; }
public Pedido(string cliente, string bebida)
{
Cliente = cliente;
Bebida = bebida;
}
}
// ❌ Esta clase se instancia cada vez, perdiendo el control central
public class RegistroPedidos
{
private List<Pedido> pedidos = new List<Pedido>();
public void AgregarPedido(Pedido pedido)
{
pedidos.Add(pedido);
Console.WriteLine($"📝 Pedido agregado: {pedido.Cliente} - {pedido.Bebida}");
}
public void MostrarPedidos()
{
Console.WriteLine("📋 Pedidos registrados:");
foreach (var pedido in pedidos)
{
Console.WriteLine($"- {pedido.Cliente}: {pedido.Bebida}");
}
}
}
class Program
{
static void Main(string[] args)
{
// ☠️ Cada barista crea su propio registro
var registroBarista1 = new RegistroPedidos();
registroBarista1.AgregarPedido(new Pedido("Ana", "Latte"));
var registroBarista2 = new RegistroPedidos();
registroBarista2.AgregarPedido(new Pedido("Luis", "Café Americano"));
// ❌ No se ven todos los pedidos, cada uno tiene su propia lista
Console.WriteLine("\nRegistro del barista 1:");
registroBarista1.MostrarPedidos();
Console.WriteLine("\nRegistro del barista 2:");
registroBarista2.MostrarPedidos();
Console.WriteLine("\n¿Dónde está la lista completa? 🤔");
}
}
}Porque la clase tenía un constructor público y en Main() cada "barista" hacía new RegistroPedidos(). Eso crea varias listas independientes en memoria.
Cada barista agrega a su propia lista privada: las entradas quedan fragmentadas y nadie tiene la vista completa.
List no es thread-safe. Accesos concurrentes pueden provocar condiciones de carrera, excepciones (modificación durante enumeración), pérdidas de datos o duplicados inconsistentes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace CafeteriaSingleton
{
public class Pedido
{
public string Cliente { get; set; }
public string Bebida { get; set; }
public Pedido(string cliente, string bebida)
{
Cliente = cliente;
Bebida = bebida;
}
public override string ToString() => $"{Cliente} - {Bebida}";
}
// Clase sellada para prevenir herencia indeseada
public sealed class RegistroPedidos
{
// Estado interno (protegido)
private readonly List<Pedido> pedidos = new List<Pedido>();
private readonly object _listaLock = new object();
// 3️⃣ Instancia estática privada
private static RegistroPedidos _instancia;
// 4️⃣ Candado para creación (lazy y thread-safe)
private static readonly object _candado = new object();
// 2️⃣ Constructor privado para evitar instancias externas
private RegistroPedidos() { }
// 5️⃣ Punto único de acceso (Singleton, con lock)
public static RegistroPedidos ObtenerInstancia()
{
lock (_candado)
{
if (_instancia == null)
{
_instancia = new RegistroPedidos();
}
return _instancia;
}
}
// 8️⃣ Métodos que protegen el estado interno (locks y validaciones)
public bool AgregarPedido(Pedido pedido)
{
if (pedido == null) return false;
lock (_listaLock)
{
// Validar duplicados (misma combinacion Cliente+Bebida)
if (pedidos.Any(p => p.Cliente == pedido.Cliente && p.Bebida == pedido.Bebida))
{
Console.WriteLine($"⚠️ Pedido duplicado detectado (evitado): {pedido}");
return false;
}
pedidos.Add(pedido);
Console.WriteLine($"📝 Pedido agregado: {pedido}");
return true;
}
}
public void MostrarPedidos()
{
lock (_listaLock)
{
Console.WriteLine("📋 Pedidos registrados:");
if (pedidos.Count == 0)
{
Console.WriteLine("- (vacío)");
return;
}
foreach (var pedido in pedidos)
{
Console.WriteLine($"- {pedido.Cliente}: {pedido.Bebida}");
}
}
}
public bool BorrarPedido(string cliente, string bebida)
{
lock (_listaLock)
{
var p = pedidos.FirstOrDefault(x => x.Cliente == cliente && x.Bebida == bebida);
if (p != null)
{
pedidos.Remove(p);
Console.WriteLine($"🗑️ Pedido borrado: {p}");
return true;
}
return false;
}
}
public void Reiniciar()
{
lock (_listaLock)
{
pedidos.Clear();
Console.WriteLine("🔁 Registro reiniciado.");
}
}
public string ExportarCSV()
{
lock (_listaLock)
{
var lines = pedidos.Select(p => $"{EscapeCsv(p.Cliente)},{EscapeCsv(p.Bebida)}");
return "Cliente,Bebida\n" + string.Join("\n", lines);
}
}
private string EscapeCsv(string s) => s?.Contains(",") == true ? $"\"{s.Replace("\"","\"\"")}\"" : s ?? "";
}
class Program
{
static void Main(string[] args)
{
// 6️⃣ Reemplazamos la creación directa por ObtenerInstancia()
var registroBarista1 = RegistroPedidos.ObtenerInstancia();
registroBarista1.AgregarPedido(new Pedido("Ana", "Latte"));
var registroBarista2 = RegistroPedidos.ObtenerInstancia();
registroBarista2.AgregarPedido(new Pedido("Luis", "Café Americano"));
// 7️⃣ Verificación: ambos "baristas" deberían ver la misma lista unificada
Console.WriteLine("\nRegistro (barista 1):");
registroBarista1.MostrarPedidos();
Console.WriteLine("\nRegistro (barista 2):");
registroBarista2.MostrarPedidos();
Console.WriteLine($"\n¿Misma instancia? {ReferenceEquals(registroBarista1, registroBarista2)}");
Console.WriteLine("\nExportando CSV:");
Console.WriteLine(RegistroPedidos.ObtenerInstancia().ExportarCSV());
// Ejemplo corto de concurrencia: arrancamos varios hilos que agregan pedidos
var threads = new List<Thread>();
for (int i = 0; i < 5; i++)
{
int id = i;
var t = new Thread(() =>
{
var reg = RegistroPedidos.ObtenerInstancia();
reg.AgregarPedido(new Pedido($"Cliente{id}", $"Bebida{id}"));
});
threads.Add(t);
t.Start();
}
foreach (var t in threads) t.Join();
Console.WriteLine("\nRegistro final tras hilos:");
RegistroPedidos.ObtenerInstancia().MostrarPedidos();
}
}
}Estado centralizado y consistente: todos los baristas ven la misma lista.
Control del acceso a la instancia (creación única).
Fácil de usar en código existente (llamadas estáticas).
Introduce estado global: puede incrementar el acoplamiento y dificultar testing.
Puede ocultar dependencias; componentes que usan RegistroPedidos.ObtenerInstancia() quedan acoplados al Singleton.
El Singleton puede complicar la gestión del ciclo de vida (cuando necesitar liberar recursos o reconfigurar).
En diseños grandes, se prefiere inyección de dependencias (DI) para control y testabilidad.
Sí, en muchos escenarios empresariales DI es preferible: permite cambiar implementaciones, facilita testing y controla scope/vida del objeto. El Singleton es útil cuando realmente necesitas una única instancia global y quieres simplicidad rápida.