Introducción a los principios SOLID en C#
\nEl desarrollo en C# dentro del ecosistema .NET requiere no solo conocimiento de sintaxis, sino también la capacidad de diseñar sistemas flexibles, mantenibles y comprobables. Para esto fueron creados los principios SOLID: cinco reglas fundamentales de la programación orientada a objetos, formuladas por Robert Martin (Tío Bob).
\nEl acrónimo SOLID se desglosa como:
\n- \n
- S — Single Responsibility Principle (Principio de responsabilidad única) \n
- O — Open/Closed Principle (Principio abierto/cerrado) \n
- L — Liskov Substitution Principle (Principio de sustitución de Liskov) \n
- I — Interface Segregation Principle (Principio de segregación de interfaces) \n
- D — Dependency Inversion Principle (Principio de inversión de dependencias) \n
En este artículo analizaremos cada principio en la práctica con ejemplos en C#, mostraremos cómo su violación genera problemas y cómo su correcta aplicación hace que el código sea más limpio.
\n\n1. Principio de responsabilidad única (SRP)
\nEsencia: Una clase debe tener solo una razón para cambiar. En otras palabras, cada clase es responsable de una tarea claramente definida.
\n\nViolación de SRP en C#
\nConsideremos un ejemplo típico: la clase OrderService, que hace demasiado:
public class OrderService\n{\n public void ProcessOrder(Order order)\n {\n // 1. Guardar en BD\n SaveToDatabase(order);\n \n // 2. Enviar email\n SendEmail(order.CustomerEmail, "Pedido realizado");\n \n // 3. Registrar en log\n File.WriteAllText("log.txt", $"Pedido {order.Id} procesado");\n }\n \n private void SaveToDatabase(Order order) { /* ... */ }\n private void SendEmail(string email, string message) { /* ... */ }\n}\nProblema: Un cambio en la lógica de envío de email o en la forma de registrar en log provocará una modificación en la clase OrderService. Esto viola SRP.
Implementación correcta de SRP
\nSeparamos las responsabilidades en servicios individuales:
\npublic class OrderProcessor\n{\n private readonly IOrderRepository _repository;\n private readonly INotificationService _notificationService;\n private readonly ILogger _logger;\n \n public OrderProcessor(\n IOrderRepository repository,\n INotificationService notificationService,\n ILogger logger)\n {\n _repository = repository;\n _notificationService = notificationService;\n _logger = logger;\n }\n \n public void ProcessOrder(Order order)\n {\n _repository.Save(order);\n _notificationService.SendConfirmation(order.CustomerEmail);\n _logger.Log($"Pedido {order.Id} procesado");\n }\n}\n\npublic interface IOrderRepository { void Save(Order order); }\npublic interface INotificationService { void SendConfirmation(string email); }\npublic interface ILogger { void Log(string message); }\nAhora cada clase es responsable solo de su tarea. Un cambio en la lógica de registro no afecta el procesamiento de pedidos.
\n\n2. Principio abierto/cerrado (OCP)
\nEsencia: Las clases deben estar abiertas para extensión, pero cerradas para modificación. Es decir, debe poder agregar nuevo comportamiento sin cambiar el código existente.
\n\nViolación de OCP en C#
\nEjemplo clásico: una calculadora con condicionales:
\npublic class AreaCalculator\n{\n public double CalculateArea(object shape)\n {\n if (shape is Rectangle rect)\n return rect.Width * rect.Height;\n else if (shape is Circle circle)\n return Math.PI * circle.Radius * circle.Radius;\n // ¡Agregar una nueva figura requiere modificar este método!\n throw new NotSupportedException();\n }\n}