martes, 23 de junio de 2026

Patrones de diseño en .NET

Los patrones de diseño son: soluciones habituales a problemas comunes en el diseño de software.

Cada patrón es como un plano que se puede personalizar para resolver un problema de diseño particular de tu código.

La siguiente imagen muestra los patrones de diseño en C#.

Patrones de diseño en .NET (C#)

Podemos ver tres tipos y sus divisiones.

  • Patrones creacionales: proporcionan varios mecanismos de creación de objetos que incrementan la flexibilidad y la reutilización del código existente. 
  • Patrones estructurales: estructurales explican cómo ensamblar objetos y clases en estructuras más grandes, a la vez que se mantiene la flexibilidad y eficiencia de estas estructuras. 
  • Patrones de comportamiento: los que tratan con algoritmos y la asignación de responsabilidades entre objetos.

Patrones Creacionales (Creational Patterns)

Factory method

Permite crear familias de objetos relacionados sin especificar sus clases concretas.

Ejemplo:

// Producto abstracto
public abstract class Documento
{
    public abstract void Mostrar();
}

// Productos concretos
public class PDF : Documento
{
    public override void Mostrar() => Console.WriteLine("Mostrando documento PDF");
}

public class Word : Documento
{
    public override void Mostrar() => Console.WriteLine("Mostrando documento Word");
}

// Creador abstracto
public abstract class CreadorDocumento
{
    public abstract Documento CrearDocumento();
}

// Creador concreto
public class CreadorPDF : CreadorDocumento
{
    public override Documento CrearDocumento() => new PDF();
}

public class CreadorWord : CreadorDocumento
{
    public override Documento CrearDocumento() => new Word();
}

// Uso del patrón
class Program
{
    static void Main()
    {
        CreadorDocumento creador = new CreadorPDF();
        Documento doc = creador.CrearDocumento();
        doc.Mostrar();

        creador = new CreadorWord();
        doc = creador.CrearDocumento();
        doc.Mostrar();
    }
}

Abstract factory

Permite crear familias de objetos relacionados sin especificar sus clases concretas.

Ejemplo:

// Productos abstractos
public interface ISilla
{
    void Sentarse();
}

public interface IMesa
{
    void Usar();
}

// Productos concretos
public class SillaModerna : ISilla
{
    public void Sentarse() => Console.WriteLine("Sentado en una silla moderna.");
}

public class MesaModerna : IMesa
{
    public void Usar() => Console.WriteLine("Usando una mesa moderna.");
}

public class SillaClasica : ISilla
{
    public void Sentarse() => Console.WriteLine("Sentado en una silla clásica.");
}

public class MesaClasica : IMesa
{
    public void Usar() => Console.WriteLine("Usando una mesa clásica.");
}

// Fábrica abstracta
public interface IFabricaMuebles
{
    ISilla CrearSilla();
    IMesa CrearMesa();
}

// Fábricas concretas
public class FabricaModerna : IFabricaMuebles
{
    public ISilla CrearSilla() => new SillaModerna();
    public IMesa CrearMesa() => new MesaModerna();
}

public class FabricaClasica : IFabricaMuebles
{
    public ISilla CrearSilla() => new SillaClasica();
    public IMesa CrearMesa() => new MesaClasica();
}

// Uso del patrón
class Program
{
    static void Main()
    {
        IFabricaMuebles fabrica = new FabricaModerna();
        ISilla silla = fabrica.CrearSilla();
        IMesa mesa = fabrica.CrearMesa();

        silla.Sentarse();
        mesa.Usar();

        fabrica = new FabricaClasica();
        silla = fabrica.CrearSilla();
        mesa = fabrica.CrearMesa();

        silla.Sentarse();
        mesa.Usar();
    }
}

Builder

Permite construir objetos complejos paso a paso. Este patrón permite generar diferentes tipos y representaciones de un objeto utilizando el mismo código de construcción.

Ejemplo:

// Producto complejo
public class Computadora
{
    public string CPU { get; set; }
    public string RAM { get; set; }
    public string Almacenamiento { get; set; }

    public void Mostrar()
    {
        Console.WriteLine($"CPU: {CPU}, RAM: {RAM}, Almacenamiento: {Almacenamiento}");
    }
}

// Builder abstracto
public interface IBuilderComputadora
{
    void ConstruirCPU();
    void ConstruirRAM();
    void ConstruirAlmacenamiento();
    Computadora ObtenerComputadora();
}

// Builder concreto
public class BuilderComputadoraGaming : IBuilderComputadora
{
    private Computadora computadora = new Computadora();

    public void ConstruirCPU() => computadora.CPU = "Intel Core i9";
    public void ConstruirRAM() => computadora.RAM = "32 GB";
    public void ConstruirAlmacenamiento() => computadora.Almacenamiento = "1 TB SSD";

    public Computadora ObtenerComputadora() => computadora;
}

public class BuilderComputadoraOficina : IBuilderComputadora
{
    private Computadora computadora = new Computadora();

    public void ConstruirCPU() => computadora.CPU = "Intel Core i5";
    public void ConstruirRAM() => computadora.RAM = "8 GB";
    public void ConstruirAlmacenamiento() => computadora.Almacenamiento = "500 GB HDD";

    public Computadora ObtenerComputadora() => computadora;
}

// Director
public class Director
{
    public void Construir(IBuilderComputadora builder)
    {
        builder.ConstruirCPU();
        builder.ConstruirRAM();
        builder.ConstruirAlmacenamiento();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        Director director = new Director();

        IBuilderComputadora builderGaming = new BuilderComputadoraGaming();
        director.Construir(builderGaming);
        Computadora pcGaming = builderGaming.ObtenerComputadora();
        pcGaming.Mostrar();

        IBuilderComputadora builderOficina = new BuilderComputadoraOficina();
        director.Construir(builderOficina);
        Computadora pcOficina = builderOficina.ObtenerComputadora();
        pcOficina.Mostrar();
    }
}

Prototype

Te permite copiar objetos existentes sin que tu código dependa de sus clases.

Ejemplo:

using System;

// Interfaz Prototype
public interface IFigura
{
    IFigura Clonar();
    void Mostrar();
}

// Clase concreta
public class Circulo : IFigura
{
    public int Radio { get; set; }

    public Circulo(int radio)
    {
        Radio = radio;
    }

    public IFigura Clonar()
    {
        // Clonación superficial
        return new Circulo(this.Radio);
    }

    public void Mostrar()
    {
        Console.WriteLine($"Círculo con radio: {Radio}");
    }
}

public class Cuadrado : IFigura
{
    public int Lado { get; set; }

    public Cuadrado(int lado)
    {
        Lado = lado;
    }

    public IFigura Clonar()
    {
        return new Cuadrado(this.Lado);
    }

    public void Mostrar()
    {
        Console.WriteLine($"Cuadrado con lado: {Lado}");
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        Circulo c1 = new Circulo(10);
        Circulo c2 = (Circulo)c1.Clonar();

        c1.Mostrar();
        c2.Mostrar();

        Cuadrado q1 = new Cuadrado(5);
        Cuadrado q2 = (Cuadrado)q1.Clonar();

        q1.Mostrar();
        q2.Mostrar();
    }
}

Singleton

Permite garantizar que una clase tenga una sola instancia, a la vez que proporciona un punto de acceso global a dicha instancia.

Ejemplo:

using System;

public sealed class Configuracion
{
    // Instancia única (lazy initialization con thread-safety)
    private static readonly Lazy<Configuracion> instancia =
        new Lazy<Configuracion>(() => new Configuracion());

    // Propiedad pública para acceder a la instancia
    public static Configuracion Instancia => instancia.Value;

    // Propiedades de ejemplo
    public string NombreAplicacion { get; private set; }

    // Constructor privado para evitar instanciación externa
    private Configuracion()
    {
        NombreAplicacion = "MiAplicacion v1.0";
    }

    // Método de ejemplo
    public void Mostrar()
    {
        Console.WriteLine($"Configuración cargada: {NombreAplicacion}");
    }
}

class Program
{
    static void Main()
    {
        // Acceder a la única instancia
        Configuracion c1 = Configuracion.Instancia;
        c1.Mostrar();

        // Intentar obtener otra instancia devuelve la misma
        Configuracion c2 = Configuracion.Instancia;
        Console.WriteLine(Object.ReferenceEquals(c1, c2)); // True
    }
}

Patrones Estructurales (Structural Patterns)

Adapter

Permite que objetos con interfaces incompatibles colaboren.

Ejemplo:

using System;

// Interfaz esperada por el cliente
public interface IReproductorAudio
{
    void Reproducir(string archivo);
}

// Clase existente con una interfaz diferente
public class ServicioMp3
{
    public void PlayMp3(string nombreArchivo)
    {
        Console.WriteLine($"Reproduciendo archivo MP3: {nombreArchivo}");
    }
}

// Adaptador que traduce entre la interfaz esperada y la clase existente
public class AdaptadorMp3 : IReproductorAudio
{
    private readonly ServicioMp3 servicioMp3;

    public AdaptadorMp3(ServicioMp3 servicio)
    {
        servicioMp3 = servicio;
    }

    public void Reproducir(string archivo)
    {
        // Traduce la llamada
        servicioMp3.PlayMp3(archivo);
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        // Cliente espera trabajar con IReproductorAudio
        IReproductorAudio reproductor = new AdaptadorMp3(new ServicioMp3());

        reproductor.Reproducir("cancion.mp3");
    }
}

Facade

Proporciona una interfaz simplificada para una biblioteca, un marco de trabajo o cualquier otro conjunto complejo de clases.

Ejemplo:

using System;

// Subsistemas
public class DVDPlayer
{
    public void Encender() => Console.WriteLine("DVD Player encendido");
    public void Reproducir() => Console.WriteLine("Reproduciendo DVD...");
}

public class Proyector
{
    public void Encender() => Console.WriteLine("Proyector encendido");
    public void ApagarLuces() => Console.WriteLine("Luces apagadas para la proyección");
}

public class SistemaSonido
{
    public void Encender() => Console.WriteLine("Sistema de sonido encendido");
    public void AjustarVolumen(int nivel) => Console.WriteLine($"Volumen ajustado a {nivel}");
}

// Facade
public class HomeTheaterFacade
{
    private DVDPlayer dvd;
    private Proyector proyector;
    private SistemaSonido sonido;

    public HomeTheaterFacade(DVDPlayer dvd, Proyector proyector, SistemaSonido sonido)
    {
        this.dvd = dvd;
        this.proyector = proyector;
        this.sonido = sonido;
    }

    public void VerPelicula()
    {
        Console.WriteLine("Preparando el sistema para ver la película...");
        proyector.Encender();
        proyector.ApagarLuces();
        sonido.Encender();
        sonido.AjustarVolumen(10);
        dvd.Encender();
        dvd.Reproducir();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        DVDPlayer dvd = new DVDPlayer();
        Proyector proyector = new Proyector();
        SistemaSonido sonido = new SistemaSonido();

        HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvd, proyector, sonido);
        homeTheater.VerPelicula();
    }
}

Brigde

Permite dividir una clase grande o un conjunto de clases estrechamente relacionadas en dos jerarquías separadas —abstracción e implementación— que pueden desarrollarse independientemente una de la otra.

Ejemplo:

using System;

// Implementador
public interface IDispositivo
{
    void Encender();
    void Apagar();
}

// Implementaciones concretas
public class Televisor : IDispositivo
{
    public void Encender() => Console.WriteLine("Televisor encendido");
    public void Apagar() => Console.WriteLine("Televisor apagado");
}

public class Radio : IDispositivo
{
    public void Encender() => Console.WriteLine("Radio encendida");
    public void Apagar() => Console.WriteLine("Radio apagada");
}

// Abstracción
public abstract class ControlRemoto
{
    protected IDispositivo dispositivo;

    protected ControlRemoto(IDispositivo dispositivo)
    {
        this.dispositivo = dispositivo;
    }

    public abstract void BotonEncender();
    public abstract void BotonApagar();
}

// Abstracción refinada
public class ControlBasico : ControlRemoto
{
    public ControlBasico(IDispositivo dispositivo) : base(dispositivo) { }

    public override void BotonEncender() => dispositivo.Encender();
    public override void BotonApagar() => dispositivo.Apagar();
}

// Uso del patrón
class Program
{
    static void Main()
    {
        IDispositivo tv = new Televisor();
        ControlRemoto controlTv = new ControlBasico(tv);

        controlTv.BotonEncender();
        controlTv.BotonApagar();

        IDispositivo radio = new Radio();
        ControlRemoto controlRadio = new ControlBasico(radio);

        controlRadio.BotonEncender();
        controlRadio.BotonApagar();
    }
}

Composite

Permite componer objetos en estructuras de árbol y luego trabajar con estas estructuras como si fueran objetos individuales.

Ejemplo:

using System;
using System.Collections.Generic;

// Componente
public abstract class MenuComponent
{
    public abstract void Mostrar();
}

// Hoja (Leaf)
public class Plato : MenuComponent
{
    private string nombre;

    public Plato(string nombre)
    {
        this.nombre = nombre;
    }

    public override void Mostrar()
    {
        Console.WriteLine($"Plato: {nombre}");
    }
}

// Compuesto (Composite)
public class Menu : MenuComponent
{
    private string nombre;
    private List<MenuComponent> elementos = new List<MenuComponent>();

    public Menu(string nombre)
    {
        this.nombre = nombre;
    }

    public void Agregar(MenuComponent componente)
    {
        elementos.Add(componente);
    }

    public override void Mostrar()
    {
        Console.WriteLine($"Menú: {nombre}");
        foreach (var elemento in elementos)
        {
            elemento.Mostrar();
        }
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        // Crear platos
        Plato sopa = new Plato("Sopa de verduras");
        Plato pasta = new Plato("Pasta al pesto");
        Plato postre = new Plato("Tiramisú");

        // Crear menú principal
        Menu menuPrincipal = new Menu("Menú Principal");
        menuPrincipal.Agregar(sopa);
        menuPrincipal.Agregar(pasta);

        // Crear menú de postres
        Menu menuPostres = new Menu("Menú de Postres");
        menuPostres.Agregar(postre);

        // Agregar menú de postres al menú principal
        menuPrincipal.Agregar(menuPostres);

        // Mostrar todo
        menuPrincipal.Mostrar();
    }
}

Proxy

Permite proporcionar un sustituto o marcador de posición para otro objeto. Un proxy controla el acceso al objeto original, lo que permite realizar alguna acción antes o después de que la solicitud llegue al objeto original.

Ejemplo:

using System;

// Interfaz común
public interface IImagen
{
    void Mostrar();
}

// Clase real (objeto pesado)
public class ImagenReal : IImagen
{
    private string archivo;

    public ImagenReal(string archivo)
    {
        this.archivo = archivo;
        CargarDesdeDisco();
    }

    private void CargarDesdeDisco()
    {
        Console.WriteLine($"Cargando imagen desde disco: {archivo}");
    }

    public void Mostrar()
    {
        Console.WriteLine($"Mostrando imagen: {archivo}");
    }
}

// Proxy
public class ProxyImagen : IImagen
{
    private string archivo;
    private ImagenReal imagenReal;

    public ProxyImagen(string archivo)
    {
        this.archivo = archivo;
    }

    public void Mostrar()
    {
        // Carga diferida: solo se crea la imagen real cuando se necesita
        if (imagenReal == null)
        {
            imagenReal = new ImagenReal(archivo);
        }
        imagenReal.Mostrar();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        IImagen imagen = new ProxyImagen("foto_vacaciones.jpg");

        // La imagen aún no se carga desde disco
        Console.WriteLine("Proxy creado, aún no se carga la imagen...");

        // Se carga y muestra solo cuando se necesita
        imagen.Mostrar();
    }
}

Patrones de comportamiento (Behavioral Patterns)

Command

Convierte una solicitud en un objeto independiente que contiene toda la información sobre la misma. Esta transformación permite pasar solicitudes como argumentos de un método, retrasar o poner en cola la ejecución de una solicitud y admitir operaciones que no se pueden deshacer.

Ejemplo:

using System;

// Interfaz Command
public interface ICommand
{
    void Ejecutar();
}

// Receptor
public class Luz
{
    public void Encender() => Console.WriteLine("La luz está encendida");
    public void Apagar() => Console.WriteLine("La luz está apagada");
}

// Comandos concretos
public class EncenderLuzCommand : ICommand
{
    private Luz luz;

    public EncenderLuzCommand(Luz luz)
    {
        this.luz = luz;
    }

    public void Ejecutar()
    {
        luz.Encender();
    }
}

public class ApagarLuzCommand : ICommand
{
    private Luz luz;

    public ApagarLuzCommand(Luz luz)
    {
        this.luz = luz;
    }

    public void Ejecutar()
    {
        luz.Apagar();
    }
}

// Invocador
public class ControlRemoto
{
    private ICommand comando;

    public void SetComando(ICommand comando)
    {
        this.comando = comando;
    }

    public void PresionarBoton()
    {
        comando.Ejecutar();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        Luz sala = new Luz();

        ICommand encender = new EncenderLuzCommand(sala);
        ICommand apagar = new ApagarLuzCommand(sala);

        ControlRemoto control = new ControlRemoto();

        control.SetComando(encender);
        control.PresionarBoton(); // La luz está encendida

        control.SetComando(apagar);
        control.PresionarBoton(); // La luz está apagada
    }
}

Observer

Permite definir un mecanismo de suscripción para notificar a varios objetos sobre cualquier evento que ocurra en el objeto que están observando.

Ejemplo:

using System;
using System.Collections.Generic;

// Interfaz del observador
public interface IObservador
{
    void Actualizar(float temperatura);
}

// Sujeto
public class EstacionClima
{
    private List<IObservador> observadores = new List<IObservador>();
    private float temperatura;

    public void Registrar(IObservador observador)
    {
        observadores.Add(observador);
    }

    public void Remover(IObservador observador)
    {
        observadores.Remove(observador);
    }

    public void SetTemperatura(float nuevaTemperatura)
    {
        temperatura = nuevaTemperatura;
        Notificar();
    }

    private void Notificar()
    {
        foreach (var obs in observadores)
        {
            obs.Actualizar(temperatura);
        }
    }
}

// Observadores concretos
public class PantallaCelular : IObservador
{
    public void Actualizar(float temperatura)
    {
        Console.WriteLine($"[Celular] Temperatura actual: {temperatura}°C");
    }
}

public class PantallaPC : IObservador
{
    public void Actualizar(float temperatura)
    {
        Console.WriteLine($"[PC] Temperatura actual: {temperatura}°C");
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        EstacionClima estacion = new EstacionClima();

        IObservador celular = new PantallaCelular();
        IObservador pc = new PantallaPC();

        estacion.Registrar(celular);
        estacion.Registrar(pc);

        estacion.SetTemperatura(25.5f);
        estacion.SetTemperatura(30.0f);
    }
}

Strategy

Permite definir una familia de algoritmos, colocar cada uno de ellos en una clase separada y hacer que sus objetos sean intercambiables.

Ejemplo:

using System;

// Estrategia (Strategy)
public interface IPago
{
    void Pagar(double monto);
}

// Estrategias concretas
public class PagoTarjeta : IPago
{
    public void Pagar(double monto)
    {
        Console.WriteLine($"Pagando {monto} con tarjeta de crédito.");
    }
}

public class PagoPayPal : IPago
{
    public void Pagar(double monto)
    {
        Console.WriteLine($"Pagando {monto} con PayPal.");
    }
}

public class PagoEfectivo : IPago
{
    public void Pagar(double monto)
    {
        Console.WriteLine($"Pagando {monto} en efectivo.");
    }
}

// Contexto
public class CarritoCompras
{
    private IPago estrategiaPago;

    public void SetEstrategiaPago(IPago estrategia)
    {
        estrategiaPago = estrategia;
    }

    public void ProcesarCompra(double monto)
    {
        if (estrategiaPago == null)
        {
            Console.WriteLine("No se ha definido una estrategia de pago.");
            return;
        }
        estrategiaPago.Pagar(monto);
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        CarritoCompras carrito = new CarritoCompras();

        carrito.SetEstrategiaPago(new PagoTarjeta());
        carrito.ProcesarCompra(500);

        carrito.SetEstrategiaPago(new PagoPayPal());
        carrito.ProcesarCompra(300);

        carrito.SetEstrategiaPago(new PagoEfectivo());
        carrito.ProcesarCompra(150);
    }
}

State

Permite que un objeto altere su comportamiento cuando cambia su estado interno. Parece como si el objeto hubiera cambiado de clase.

Ejemplo:

using System;

// Interfaz Estado
public interface IEstadoCafe
{
    void PresionarBoton();
}

// Estados concretos
public class EstadoSinMoneda : IEstadoCafe
{
    public void PresionarBoton()
    {
        Console.WriteLine("Inserta una moneda primero.");
    }
}

public class EstadoConMoneda : IEstadoCafe
{
    public void PresionarBoton()
    {
        Console.WriteLine("Preparando café...");
    }
}

// Contexto
public class MaquinaCafe
{
    private IEstadoCafe estado;

    public MaquinaCafe()
    {
        estado = new EstadoSinMoneda();
    }

    public void InsertarMoneda()
    {
        Console.WriteLine("Moneda insertada.");
        estado = new EstadoConMoneda();
    }

    public void PresionarBoton()
    {
        estado.PresionarBoton();
        // Después de servir café, vuelve al estado inicial
        estado = new EstadoSinMoneda();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        MaquinaCafe maquina = new MaquinaCafe();

        maquina.PresionarBoton(); // Inserta una moneda primero
        maquina.InsertarMoneda(); // Moneda insertada
        maquina.PresionarBoton(); // Preparando café...
        maquina.PresionarBoton(); // Inserta una moneda primero
    }
}

Template Method

Define el esqueleto de un algoritmo en la superclase, pero permite que las subclases anulen pasos específicos del algoritmo sin cambiar su estructura.

Ejemplo:

using System;

// Clase abstracta con el método plantilla
public abstract class BebidaCaliente
{
    // Método plantilla
    public void Preparar()
    {
        HervirAgua();
        PrepararInfusion();
        ServirEnTaza();
        AgregarExtras();
    }

    private void HervirAgua()
    {
        Console.WriteLine("Hirviendo agua...");
    }

    private void ServirEnTaza()
    {
        Console.WriteLine("Sirviendo en la taza...");
    }

    // Métodos abstractos que las subclases deben implementar
    protected abstract void PrepararInfusion();
    protected abstract void AgregarExtras();
}

// Subclase concreta: Té
public class Te : BebidaCaliente
{
    protected override void PrepararInfusion()
    {
        Console.WriteLine("Colocando bolsita de té...");
    }

    protected override void AgregarExtras()
    {
        Console.WriteLine("Agregando limón.");
    }
}

// Subclase concreta: Café
public class Cafe : BebidaCaliente
{
    protected override void PrepararInfusion()
    {
        Console.WriteLine("Agregando café molido al filtro...");
    }

    protected override void AgregarExtras()
    {
        Console.WriteLine("Agregando azúcar y leche.");
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        BebidaCaliente te = new Te();
        Console.WriteLine("Preparando té:");
        te.Preparar();

        Console.WriteLine();

        BebidaCaliente cafe = new Cafe();
        Console.WriteLine("Preparando café:");
        cafe.Preparar();
    }
}

Visitor

Permite separar los algoritmos de los objetos sobre los que operan.

Ejemplo:

using System;
using System.Collections.Generic;

// Interfaz del Visitor
public interface IVisitor
{
    void VisitarCirculo(Circulo circulo);
    void VisitarCuadrado(Cuadrado cuadrado);
}

// Elemento abstracto
public interface IFigura
{
    void Aceptar(IVisitor visitor);
}

// Elementos concretos
public class Circulo : IFigura
{
    public int Radio { get; set; }

    public Circulo(int radio)
    {
        Radio = radio;
    }

    public void Aceptar(IVisitor visitor)
    {
        visitor.VisitarCirculo(this);
    }
}

public class Cuadrado : IFigura
{
    public int Lado { get; set; }

    public Cuadrado(int lado)
    {
        Lado = lado;
    }

    public void Aceptar(IVisitor visitor)
    {
        visitor.VisitarCuadrado(this);
    }
}

// Visitor concreto: calcular área
public class VisitorArea : IVisitor
{
    public void VisitarCirculo(Circulo circulo)
    {
        double area = Math.PI * circulo.Radio * circulo.Radio;
        Console.WriteLine($"Área del círculo: {area}");
    }

    public void VisitarCuadrado(Cuadrado cuadrado)
    {
        double area = cuadrado.Lado * cuadrado.Lado;
        Console.WriteLine($"Área del cuadrado: {area}");
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        List<IFigura> figuras = new List<IFigura>
        {
            new Circulo(5),
            new Cuadrado(4)
        };

        IVisitor visitorArea = new VisitorArea();

        foreach (var figura in figuras)
        {
            figura.Aceptar(visitorArea);
        }
    }
}

Chain of Responsibility

Permite que las solicitudes se transmitan a través de una cadena de manejadores. Al recibir una solicitud, cada manejador decide si la procesa o la pasa al siguiente manejador de la cadena.

Ejemplo:

using System;

// Interfaz Handler
public abstract class Aprobador
{
    protected Aprobador siguiente;

    public void SetSiguiente(Aprobador aprobador)
    {
        siguiente = aprobador;
    }

    public abstract void ProcesarSolicitud(double monto);
}

// Handlers concretos
public class Jefe : Aprobador
{
    public override void ProcesarSolicitud(double monto)
    {
        if (monto <= 1000)
        {
            Console.WriteLine($"Jefe aprueba gasto de {monto}.");
        }
        else if (siguiente != null)
        {
            siguiente.ProcesarSolicitud(monto);
        }
    }
}

public class Gerente : Aprobador
{
    public override void ProcesarSolicitud(double monto)
    {
        if (monto <= 5000)
        {
            Console.WriteLine($"Gerente aprueba gasto de {monto}.");
        }
        else if (siguiente != null)
        {
            siguiente.ProcesarSolicitud(monto);
        }
    }
}

public class Director : Aprobador
{
    public override void ProcesarSolicitud(double monto)
    {
        if (monto <= 20000)
        {
            Console.WriteLine($"Director aprueba gasto de {monto}.");
        }
        else
        {
            Console.WriteLine($"Solicitud de {monto} requiere junta directiva.");
        }
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        // Crear la cadena de responsabilidad
        Aprobador jefe = new Jefe();
        Aprobador gerente = new Gerente();
        Aprobador director = new Director();

        jefe.SetSiguiente(gerente);
        gerente.SetSiguiente(director);

        // Probar solicitudes
        jefe.ProcesarSolicitud(500);    // Jefe aprueba
        jefe.ProcesarSolicitud(3000);   // Gerente aprueba
        jefe.ProcesarSolicitud(15000);  // Director aprueba
        jefe.ProcesarSolicitud(50000);  // Junta directiva
    }
}

Memento

Permite guardar y restaurar el estado anterior de un objeto sin revelar los detalles de su implementación.

Ejemplo:

using System;
using System.Collections.Generic;

// Memento: guarda el estado
public class Memento
{
    public string Estado { get; }

    public Memento(string estado)
    {
        Estado = estado;
    }
}

// Originator: crea y restaura mementos
public class EditorTexto
{
    public string Texto { get; set; }

    public Memento Guardar()
    {
        return new Memento(Texto);
    }

    public void Restaurar(Memento memento)
    {
        Texto = memento.Estado;
    }
}

// Caretaker: administra los mementos
public class Historial
{
    private Stack<Memento> estados = new Stack<Memento>();

    public void Agregar(Memento memento)
    {
        estados.Push(memento);
    }

    public Memento Deshacer()
    {
        return estados.Pop();
    }
}

// Uso del patrón
class Program
{
    static void Main()
    {
        EditorTexto editor = new EditorTexto();
        Historial historial = new Historial();

        editor.Texto = "Versión 1";
        historial.Agregar(editor.Guardar());

        editor.Texto = "Versión 2";
        historial.Agregar(editor.Guardar());

        editor.Texto = "Versión 3";
        Console.WriteLine($"Texto actual: {editor.Texto}");

        // Deshacer
        editor.Restaurar(historial.Deshacer());
        Console.WriteLine($"Después de deshacer: {editor.Texto}");

        editor.Restaurar(historial.Deshacer());
        Console.WriteLine($"Después de deshacer otra vez: {editor.Texto}");
    }
}

Importancia de los patrones de diseño

  • Ayudan a resolver problemas comunes de forma probada y reutilizable, además hacen que el código sea más limpio, flexible y profesional.
  • Mejoran la organización del código.
  • Facilitan el mantenimiento y la escalabilidad.
  • Promueven buenas prácticas de desarrollo.
  • Permiten que otros desarrolladores entiendan más rápido el sistema.

Continuaremos con este tema en próximas entregas.

Enlaces:

https://comunidadcityjavamex.blogspot.com/2026/06/java-tip-32-patrones-de-diseno-en-java.html
https://refactoring.guru/design-patterns/csharp


Patrones de diseño en .NET

Los patrones de diseño son: soluciones habituales a problemas comunes en el diseño de software . Cada patrón es como un plano que s...

Etiquetas

Archivo del blog