Contenido del curso
Principio de responsabilidad única
Principio de abierto/cerrado
Principio de sustitución de Liskov
Principio de segregación de la interfaz
Principio de inversión de la dependencia
Cierre
Refactorizar StudentRepository con principio SOLID
Resumen
El principio de responsabilidad única en C# obliga a que cada clase tenga una sola razón para cambiar. Aquí verás cómo refactorizar una clase StudentRepository que mezcla la manipulación de datos con la exportación a CSV, separando responsabilidades para cumplir con el primer pilar de SOLID.
¿Qué problema tiene la clase StudentRepository?
La clase StudentRepository debería encargarse únicamente de manipular datos del modelo Student: obtener, insertar, actualizar y eliminar. Esas son las cuatro acciones típicas de un repositorio. Sin embargo, al revisar el código, aparece un método Export que toma toda la colección, la procesa y la guarda como archivo CSV.
Y ahí está el conflicto. Exportar datos no es responsabilidad de un repositorio. Esa lógica extra rompe el principio porque la clase ahora tiene dos razones para cambiar: una si modifica la forma de gestionar estudiantes, y otra si cambia el formato de exportación.
¿Qué es el principio de responsabilidad única? Establece que una clase debe tener una sola responsabilidad y, por lo tanto, una sola razón para cambiar. Si una clase hace dos cosas distintas, debes separarlas.
¿Cómo se ve el repositorio antes del refactor?
El proyecto incluye tres piezas relevantes [01:50]:
- El modelo
Student, conId,FullName,Gradesy dos constructores. - La clase
FakeStorage, que usa generics para manejar colecciones en memoria sin base de datos. - La clase
StudentRepository, que llama aInitDataen su constructor para crear tres estudiantes de prueba y expone un métodoGetAll.
Hasta ahí todo coherente. El método GetAll retorna la colección y encaja con la responsabilidad del repositorio. El problema aparece con Export, que internamente invoca GetAll, arma la estructura CSV y escribe el archivo en disco [03:30].
¿Cómo refactorizar para cumplir el principio?
La solución es extraer la lógica de exportación a una clase nueva. Puedes crear un StudentExporter específico, o ir más lejos y construir un ExportHelper genérico que sirva para cualquier modelo de la aplicación. La decisión depende del enfoque que quieras darle al diseño.
En el demo se crea un archivo ExportHelper.cs dentro del mismo namespace SingleResponsability [05:30]. La clase expone un método ExportStudents que recibe la colección como parámetro y contiene toda la rutina que antes vivía en el repositorio.
csharp public class ExportHelper { public void ExportStudents(List<Student> students) { // lógica de construcción del CSV // requiere using System.Text; } }
Fíjate en un detalle importante: si añadieras un GetAllStudents dentro de ExportHelper, volverías a violar el principio. La clase nueva existe solo para exportar, nada más.
¿Cómo queda la clase Program después del cambio?
En Program se eliminan las llamadas viejas y se distribuye la responsabilidad entre dos objetos [07:40]:
- Crear el
StudentRepositoryy pedirle los datos conGetAll. - Instanciar
ExportHelper. - Pasar la colección al método
ExportStudents.
csharp var repository = new StudentRepository(); var students = repository.GetAll();
var exporter = new ExportHelper(); exporter.ExportStudents(students);
Al ejecutar dotnet build y dotnet run, el archivo student.csv se genera igual que antes, separado por punto y coma, con la misma información. La salida no cambia, pero la arquitectura sí.
¿Por qué importa separar la exportación del repositorio? Porque si mañana necesitas exportar a JSON, XML o cambiar el separador del CSV, modificas solo
ExportHelpersin tocar el acceso a datos. Así reduces el riesgo de romper cosas que no estás editando.
¿Cómo nombrar bien las clases que separan responsabilidades?
El nombre ExportHelper funciona, pero puede ser más descriptivo. Si la clase exporta exclusivamente a CSV, llamarla CsvExportHelper deja clara la intención. Y si soportaras varios formatos, conviene parametrizar el tipo de salida con un código o enumeración.
La lección de fondo: el nombre de una clase debe reflejar su única responsabilidad. Si tienes que escribir una conjunción ("y", "además") para describir lo que hace, probablemente está haciendo demasiado.
¿Qué reto queda pendiente con generics?
El método ExportStudents quedó atado al modelo Student. Para hacerlo verdaderamente reutilizable, puedes refactorizarlo usando generics y convertirlo en algo como Export<T>(List<T> items), de modo que ExportHelper sirva para cualquier modelo de la aplicación: estudiantes, profesores, cursos o lo que venga después [10:50].
Ese cambio mantiene el principio de responsabilidad única y, además, abre la puerta al siguiente principio SOLID: el de abierto y cerrado, que permite extender comportamientos sin modificar el código existente.
¿Cómo aplicarías generics a ExportHelper en tu propio proyecto? Comparte tu propuesta en los comentarios.