Si quiero guardar y recuperar un objeto, ¿debería crear otra clase para manejarlo, o sería mejor hacerlo en la clase misma? ¿O tal vez mezclando ambos?
¿Cuál se recomienda según el paradigma de OOD?
Por ejemplo
Class Student
{
public string Name {set; get;}
....
public bool Save()
{
SqlConnection con = ...
// Save the class in the db
}
public bool Retrieve()
{
// search the db for the student and fill the attributes
}
public List<Student> RetrieveAllStudents()
{
// this is such a method I have most problem with it
// that an object returns an array of objects of its own class!
}
}
Versus. (Sé que se recomienda lo siguiente, sin embargo, me parece un poco en contra de la cohesión de la Student
clase)
Class Student { /* */ }
Class DB {
public bool AddStudent(Student s)
{
}
public Student RetrieveStudent(Criteria)
{
}
public List<Student> RetrieveAllStudents()
{
}
}
¿Qué tal mezclarlos?
Class Student
{
public string Name {set; get;}
....
public bool Save()
{
/// do some business logic!
db.AddStudent(this);
}
public bool Retrieve()
{
// build the criteria
db.RetrieveStudent(criteria);
// fill the attributes
}
}
design
object-oriented
Ahmad
fuente
fuente
Respuestas:
Principio de responsabilidad única , separación de preocupaciones y cohesión funcional . Si lees sobre estos conceptos, la respuesta que obtienes es: sepáralos .
Una razón simple para separar la
Student
clase de la "DB" (oStudentRepository
, para seguir las convenciones más populares) es permitirle cambiar sus "reglas de negocios", presentes en laStudent
clase, sin afectar el código responsable de la persistencia, y viceversa. viceversaEste tipo de separación es muy importante, no solo entre las reglas comerciales y la persistencia, sino también entre las muchas preocupaciones de su sistema, para permitirle realizar cambios con un impacto mínimo en módulos no relacionados (mínimo porque a veces es inevitable). Ayuda a construir sistemas más robustos, más fáciles de mantener y más confiables cuando hay cambios constantes.
Al mezclar las reglas de negocio y la persistencia, ya sea una sola clase como en su primer ejemplo, o
DB
como una dependencia deStudent
, está acoplando dos preocupaciones muy diferentes. Puede parecer que pertenecen juntos; parecen ser coherentes porque usan los mismos datos. Pero aquí está la cosa: la cohesión no puede medirse únicamente por los datos que se comparten entre los procedimientos, también debe considerar el nivel de abstracción en el que existen. De hecho, el tipo ideal de cohesión se describe como:Y claramente, realizar validaciones
Student
durante un tiempo también persistiendo no forma "una sola tarea bien definida". De nuevo, las reglas de negocio y los mecanismos de persistencia son dos aspectos muy diferentes del sistema, que según muchos principios de buen diseño orientado a objetos, deben mantenerse separados.Recomiendo leer sobre Arquitectura limpia , ver esto habla sobre el Principio de responsabilidad única (donde se usa un ejemplo muy similar), y ver esto también sobre Arquitectura limpia . Estos conceptos resumen las razones detrás de tales separaciones.
fuente
Student
clase debería estar correctamente encapsulada, ¿sí? ¿Cómo puede una clase externa gestionar la persistencia del estado privado? La encapsulación tiene que equilibrarse con SoC y SRP, pero simplemente elegir uno sobre el otro sin sopesar cuidadosamente las compensaciones probablemente sea incorrecto. Una posible solución a este enigma es usar accesos privados de paquete para que el código de persistencia lo use.Ambos enfoques violan el principio de responsabilidad única. Su primera versión otorga a la
Student
clase muchas responsabilidades y la vincula a una tecnología de acceso a base de datos específica. El segundo conduce a una granDB
clase que será responsable no solo de los estudiantes, sino de cualquier otro tipo de objeto de datos en su programa. EDITAR: su tercer enfoque es el peor, ya que crea una dependencia cíclica entre la clase DB y laStudent
clase.Entonces, si no vas a escribir un programa de juguetes, no uses ninguno. En su lugar, use una clase diferente como
StudentRepository
para proporcionar una API para cargar y guardar, suponiendo que va a implementar el código CRUD por su cuenta. También puede considerar usar un marco ORM , que puede hacer el trabajo duro por usted (y el marco generalmente impondrá algunas decisiones en las que se deben colocar las operaciones de Cargar y Guardar).fuente
Existen bastantes patrones que pueden usarse para la persistencia de datos. Existe el patrón de Unidad de Trabajo , hay un patrón de Repositorio , hay algunos patrones adicionales que pueden usarse como la Fachada Remota, y así sucesivamente.
La mayoría de ellos tienen sus admiradores y sus críticos. A menudo se trata de elegir lo que parece adaptarse mejor a la aplicación y apegarse a ella (con todas sus ventajas y desventajas, es decir, no usar ambos patrones al mismo tiempo ... a menos que esté realmente seguro de eso).
Como nota al margen: en su ejemplo, RetrieveStudent, AddStudent debe ser un método estático (porque no depende de la instancia).
Otra forma de tener métodos de guardar / cargar en clase es:
Personalmente, usaría este enfoque solo en aplicaciones bastante pequeñas, posiblemente herramientas para uso personal o donde pueda predecir de manera confiable que no tendré casos de uso más complicados que solo guardar o cargar objetos.
También personalmente, vea el patrón de la Unidad de Trabajo. Cuando lo conoces, es realmente bueno tanto en casos pequeños como grandes. Y es compatible con muchos frameworks / apis, por ejemplo, para nombrar EntityFramework o RavenDB.
fuente
Si es una aplicación muy simple donde el objeto está más o menos vinculado al almacén de datos y viceversa (es decir, puede considerarse como una propiedad del almacén de datos), entonces tener un método .save () para esa clase podría tener sentido.
Pero creo que eso sería bastante excepcional.
Por el contrario, generalmente es mejor dejar que la clase administre sus datos y su funcionalidad (como un buen ciudadano OO), y externalizar el mecanismo de persistencia a otra clase o conjunto de clases.
La otra opción es usar un marco de persistencia que defina la persistencia de manera declarativa (como con las anotaciones), pero que todavía está externalizando la persistencia.
fuente
Sobre el
RetrieveAllStudents()
método, su sensación es correcta, de hecho es probable que esté fuera de lugar, porque podría tener múltiples listas distintas de estudiantes. ¿Por qué no simplemente mantener la (s) lista (s) fuera de laStudent
clase?fuente