Los patrones de diseño son soluciones reutilizables a problemas comunes que se presentan en el desarrollo de software. Estos patrones no son recetas exactas, sino guías que pueden adaptarse a diferentes situaciones. En este módulo, exploraremos algunos de los patrones de diseño más utilizados en C# y cómo implementarlos.

Contenido

Introducción a los Patrones de Diseño

Los patrones de diseño se dividen en tres categorías principales:

  • Patrones Creacionales: Se enfocan en la creación de objetos.
  • Patrones Estructurales: Se enfocan en la composición de clases y objetos.
  • Patrones de Comportamiento: Se enfocan en la interacción y responsabilidad entre objetos.

A continuación, veremos algunos ejemplos de cada categoría.

Patrón Singleton

Descripción

El patrón Singleton asegura que una clase tenga una única instancia y proporciona un punto de acceso global a ella.

Implementación

public class Singleton
{
    private static Singleton _instance;

    // Constructor privado para evitar la instanciación directa
    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }

    public void DoSomething()
    {
        Console.WriteLine("Singleton instance is working.");
    }
}

Explicación

  • Constructor Privado: Evita que se creen instancias directamente.
  • Método Instance: Proporciona un punto de acceso global a la instancia única.

Ejemplo de Uso

class Program
{
    static void Main(string[] args)
    {
        Singleton singleton = Singleton.Instance;
        singleton.DoSomething();
    }
}

Patrón Factory

Descripción

El patrón Factory define una interfaz para crear un objeto, pero deja que las subclases decidan qué clase instanciar.

Implementación

public abstract class Product
{
    public abstract void DoWork();
}

public class ConcreteProductA : Product
{
    public override void DoWork()
    {
        Console.WriteLine("ConcreteProductA is working.");
    }
}

public class ConcreteProductB : Product
{
    public override void DoWork()
    {
        Console.WriteLine("ConcreteProductB is working.");
    }
}

public class ProductFactory
{
    public static Product CreateProduct(string type)
    {
        switch (type)
        {
            case "A":
                return new ConcreteProductA();
            case "B":
                return new ConcreteProductB();
            default:
                throw new ArgumentException("Invalid type");
        }
    }
}

Explicación

  • Clase Abstracta Product: Define una interfaz para los productos.
  • Clases Concretas ConcreteProductA y ConcreteProductB: Implementan la interfaz Product.
  • Clase ProductFactory: Contiene el método CreateProduct que decide qué clase instanciar.

Ejemplo de Uso

class Program
{
    static void Main(string[] args)
    {
        Product productA = ProductFactory.CreateProduct("A");
        productA.DoWork();

        Product productB = ProductFactory.CreateProduct("B");
        productB.DoWork();
    }
}

Patrón Observer

Descripción

El patrón Observer define una dependencia uno-a-muchos entre objetos, de manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.

Implementación

public interface IObserver
{
    void Update();
}

public class ConcreteObserver : IObserver
{
    public void Update()
    {
        Console.WriteLine("Observer has been updated.");
    }
}

public class Subject
{
    private List<IObserver> _observers = new List<IObserver>();

    public void Attach(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.Update();
        }
    }
}

Explicación

  • Interfaz IObserver: Define el método Update que los observadores deben implementar.
  • Clase ConcreteObserver: Implementa la interfaz IObserver.
  • Clase Subject: Mantiene una lista de observadores y los notifica cuando cambia de estado.

Ejemplo de Uso

class Program
{
    static void Main(string[] args)
    {
        Subject subject = new Subject();
        IObserver observer = new ConcreteObserver();

        subject.Attach(observer);
        subject.Notify();
    }
}

Patrón Decorator

Descripción

El patrón Decorator permite agregar funcionalidades a un objeto de manera dinámica.

Implementación

public abstract class Component
{
    public abstract void Operation();
}

public class ConcreteComponent : Component
{
    public override void Operation()
    {
        Console.WriteLine("ConcreteComponent operation.");
    }
}

public abstract class Decorator : Component
{
    protected Component _component;

    public Decorator(Component component)
    {
        _component = component;
    }

    public override void Operation()
    {
        _component.Operation();
    }
}

public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(Component component) : base(component) { }

    public override void Operation()
    {
        base.Operation();
        Console.WriteLine("ConcreteDecoratorA additional operation.");
    }
}

Explicación

  • Clase Abstracta Component: Define la interfaz para los componentes.
  • Clase ConcreteComponent: Implementa la interfaz Component.
  • Clase Abstracta Decorator: Extiende Component y contiene una referencia a un objeto Component.
  • Clase ConcreteDecoratorA: Extiende Decorator y agrega funcionalidades adicionales.

Ejemplo de Uso

class Program
{
    static void Main(string[] args)
    {
        Component component = new ConcreteComponent();
        Decorator decorator = new ConcreteDecoratorA(component);

        decorator.Operation();
    }
}

Patrón Strategy

Descripción

El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables.

Implementación

public interface IStrategy
{
    void Execute();
}

public class ConcreteStrategyA : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("ConcreteStrategyA execution.");
    }
}

public class ConcreteStrategyB : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("ConcreteStrategyB execution.");
    }
}

public class Context
{
    private IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void ExecuteStrategy()
    {
        _strategy.Execute();
    }
}

Explicación

  • Interfaz IStrategy: Define el método Execute que las estrategias deben implementar.
  • Clases ConcreteStrategyA y ConcreteStrategyB: Implementan la interfaz IStrategy.
  • Clase Context: Mantiene una referencia a una estrategia y la ejecuta.

Ejemplo de Uso

class Program
{
    static void Main(string[] args)
    {
        Context context = new Context(new ConcreteStrategyA());
        context.ExecuteStrategy();

        context.SetStrategy(new ConcreteStrategyB());
        context.ExecuteStrategy();
    }
}

Ejercicios Prácticos

Ejercicio 1: Implementar el Patrón Singleton

Descripción: Implementa una clase Singleton que mantenga un contador de instancias creadas.

Solución:

public class SingletonCounter
{
    private static SingletonCounter _instance;
    private static int _counter = 0;

    private SingletonCounter()
    {
        _counter++;
    }

    public static SingletonCounter Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new SingletonCounter();
            }
            return _instance;
        }
    }

    public int GetCounter()
    {
        return _counter;
    }
}

class Program
{
    static void Main(string[] args)
    {
        SingletonCounter instance1 = SingletonCounter.Instance;
        SingletonCounter instance2 = SingletonCounter.Instance;

        Console.WriteLine(instance1.GetCounter()); // Output: 1
        Console.WriteLine(instance2.GetCounter()); // Output: 1
    }
}

Ejercicio 2: Implementar el Patrón Factory

Descripción: Implementa una fábrica que cree diferentes tipos de vehículos (Coche, Moto).

Solución:

public abstract class Vehicle
{
    public abstract void Drive();
}

public class Car : Vehicle
{
    public override void Drive()
    {
        Console.WriteLine("Driving a car.");
    }
}

public class Motorcycle : Vehicle
{
    public override void Drive()
    {
        Console.WriteLine("Riding a motorcycle.");
    }
}

public class VehicleFactory
{
    public static Vehicle CreateVehicle(string type)
    {
        switch (type)
        {
            case "Car":
                return new Car();
            case "Motorcycle":
                return new Motorcycle();
            default:
                throw new ArgumentException("Invalid type");
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Vehicle car = VehicleFactory.CreateVehicle("Car");
        car.Drive();

        Vehicle motorcycle = VehicleFactory.CreateVehicle("Motorcycle");
        motorcycle.Drive();
    }
}

Conclusión

En esta sección, hemos explorado algunos de los patrones de diseño más comunes y cómo implementarlos en C#. Estos patrones son herramientas poderosas que pueden ayudarte a escribir código más limpio, reutilizable y mantenible. A medida que avances en tu carrera como desarrollador, te encontrarás con estos patrones en muchas situaciones, y comprender cómo y cuándo usarlos te convertirá en un programador más eficiente y efectivo.

En el próximo módulo, profundizaremos en las mejores prácticas y estándares de codificación para asegurarnos de que tu código no solo funcione bien, sino que también sea fácil de leer y mantener. ¡Sigue adelante!

© Copyright 2024. Todos los derechos reservados