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
ConcreteProductAyConcreteProductB: Implementan la interfazProduct. - Clase
ProductFactory: Contiene el métodoCreateProductque 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étodoUpdateque los observadores deben implementar. - Clase
ConcreteObserver: Implementa la interfazIObserver. - 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 interfazComponent. - Clase Abstracta
Decorator: ExtiendeComponenty contiene una referencia a un objetoComponent. - Clase
ConcreteDecoratorA: ExtiendeDecoratory 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étodoExecuteque las estrategias deben implementar. - Clases
ConcreteStrategyAyConcreteStrategyB: Implementan la interfazIStrategy. - 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!
Curso de Programación en C#
Módulo 1: Introducción a C#
- Introducción a C#
- Configuración del Entorno de Desarrollo
- Programa Hola Mundo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
Módulo 2: Estructuras de Control
Módulo 3: Programación Orientada a Objetos
- Clases y Objetos
- Métodos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
Módulo 4: Conceptos Avanzados de C#
- Interfaces
- Delegados y Eventos
- Genéricos
- Colecciones
- LINQ (Consulta Integrada en el Lenguaje)
- Programación Asíncrona
Módulo 5: Trabajando con Datos
Módulo 6: Temas Avanzados
- Reflexión
- Atributos
- Programación Dinámica
- Gestión de Memoria y Recolección de Basura
- Multihilo y Programación Paralela
Módulo 7: Construcción de Aplicaciones
Módulo 8: Mejores Prácticas y Patrones de Diseño
- Estándares de Codificación y Mejores Prácticas
- Patrones de Diseño
- Pruebas Unitarias
- Revisión y Refactorización de Código
