Patrones de Diseño y Buenas Prácticas en C# .NET
Patrones de Diseño Creacionales
Singleton
Propósito: Evitar instancias duplicadas.
public class S
{
private static S _i;
private S() { }
public static S Instance
{
get
{
if (_i == null) _i = new S();
return _i;
}
}
}Factory Method
Propósito: Uso de if/else para crear objetos.
public abstract class C
{
public P Crear(string t)
{
var p = CrearP(t);
p.Init();
return p;
}
protected abstract P CrearP(string t);
}
public class CC : C
{
protected override P CrearP(string t)
{
if (t == "A") return new PA();
throw new ArgumentException();
}
}Patrones de Diseño Estructurales
Facade
Propósito: Varias clases llaman a los mismos servicios.
public class F
{
private A _a = new A();
private B _b = new B();
public void Op(string id)
{
_a.P1(id);
_b.P2(id);
}
}
public class C
{
private F _f;
public C(F f) => _f = f;
public void Op(string id) => _f.Op(id);
}Patrones de Diseño de Comportamiento
Template Method
Propósito: Mismos pasos, varía el contenido.
public abstract class B
{
public void Exec()
{
Pre();
Step();
Post();
}
protected virtual void Pre() { }
protected abstract void Step();
protected virtual void Post() { }
}
public class C : B
{
protected override void Step() => Console.WriteLine("paso");
}Strategy
Propósito: Uso de if/else por comportamiento.
public interface IS { void Exec(string d); }
public class C
{
private IS _s;
public C(IS s) => _s = s;
public void Do(string d) => _s.Exec(d);
}Observer
Propósito: Notificar a varios cuando cambia un estado.
public interface IO { void Update(string e); }
public class Sub
{
private List<IO> _obs = new();
public void Sub(IO o) => _obs.Add(o);
public void Unsub(IO o) => _obs.Remove(o);
private void Notify(string e)
{
foreach (var o in _obs) o.Update(e);
}
public void Change(string s) => Notify(s);
}Configuración de Entity Framework Core
public class Ctx : DbContext
{
public DbSet<E> Es { get; set; }
public Ctx(DbContextOptions<Ctx> o) : base(o)
{
if (Database.IsSqlServer()) Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<E>().Property(e => e.N).IsRequired().HasMaxLength(100);
mb.Entity<H>().HasOne(h => h.P).WithMany(p => p.Hs).HasForeignKey(h => h.PId);
}
}Configuración en appsettings.json:"DefaultConnection": "Server=localhost,1433;Database=DB;User Id=sa;Password=Pass;TrustServerCertificate=true;"
Inyección de Dependencias:
builder.Services.AddDbContext<Ctx>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IRepo, RepoSql>();Carga de Datos en EF
- Eager Loading:
var list = _ctx.Libros.Include(l => l.Autor).ToList(); - Eager Loading Anidado:
var list = _ctx.Ps.Include(p => p.Ts).ThenInclude(t => t.E).ToList(); - Explicit Loading (Objeto único):
_ctx.Entry(l).Reference(l => l.Autor).Load(); - Explicit Loading (Lista):
_ctx.Entry(l).Collection(l => l.Rs).Load();
Implementación de DTOs y Servicios
public class Req { public string C { get; set; } public E ToEntity() => new() { C = this.C }; }
public class Res { public string C { get; set; } public static Res FromEntity(E e) => new() { C = e.C }; }
public class Svc
{
public void Crear(Req r) { _repo.Save(r.ToEntity()); }
public Res Get(int id) => Res.FromEntity(_repo.GetById(id));
}Desarrollo Frontend con Blazor
@page "/ruta"
@inject IServicio Svc
@inject NavigationManager Nav
<button @onclick="Volver">Volver</button>
@code {
private List<E> lista = new();
protected override void OnInitialized() { lista = Svc.GetAll(); }
private void Volver() => Nav.NavigateTo("/");
}Ciclos de vida en DI:
- AddSingleton: Persistencia en memoria.
- AddScoped: Ciclo de vida por solicitud (ideal para EF Core).
- AddTransient: Creación de nueva instancia cada vez (helpers).
Pruebas Unitarias y Testing
[TestClass]
public class T
{
private Svc _sut;
private ICol _fake;
[TestInitialize]
public void S()
{
_fake = new FakeCol();
_sut = new Svc(_fake);
}
[TestMethod]
public void M_C_R()
{
var r = _sut.M("d");
Assert.AreEqual("e", r);
}
}- Asserts comunes: AreEqual, IsNull, IsNotNull, IsTrue, IsFalse.
- Excepciones esperadas:
[ExpectedException(typeof(ArgEx))] - Base de datos en memoria:
.UseInMemoryDatabase(Guid.NewGuid().ToString()) - Stub:
public class CS : IC { private DateTime _h; public CS(DateTime h) => _h = h; public DateTime Now => _h; }
TDD (Test Driven Development): ✅ Cada if/else/throw tiene su test. ❌ Un throw sin test no es TDD.
Principios FIRST:
- F (Fast): Rápido.
- I (Independent): Independiente.
- R (Repeatable): Repetible.
- S (Self-validating): Tiene Assert.
- T (Timely): Test antes del código.
Principios SOLID y Señales de Diseño
Señales de violación de SOLID
- SRP (Single Responsibility): La clase hace demasiado.
- OCP (Open/Closed): Agregar funcionalidad obliga a modificar el código.
- DIP (Dependency Inversion): Uso de “new” para instanciar clases concretas.
- ISP (Interface Segregation): La clase usa métodos que no necesita.
Señales para aplicar Patrones
- Singleton: Cuando hay instancias duplicadas.
- Factory: Cuando hay if/else para crear objetos.
- Facade: Cuando se llaman a los mismos servicios repetidamente.
- Template: Cuando hay mismos pasos pero varía el contenido.
- Strategy: Cuando hay if/else basados en comportamiento.
- Observer: Cuando se requiere notificar a varios componentes.
