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
yConcreteProductB
: Implementan la interfazProduct
. - Clase
ProductFactory
: Contiene el métodoCreateProduct
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étodoUpdate
que 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
: ExtiendeComponent
y contiene una referencia a un objetoComponent
. - Clase
ConcreteDecoratorA
: ExtiendeDecorator
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étodoExecute
que las estrategias deben implementar. - Clases
ConcreteStrategyA
yConcreteStrategyB
: 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