Si je veux enregistrer et récupérer un objet, dois-je créer une autre classe pour le gérer, ou serait-il préférable de le faire dans la classe elle-même? Ou peut-être mélanger les deux?
Quel est recommandé selon le paradigme OOD?
Par exemple
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!
}
}
Contre. (Je sais que ce qui suit est recommandé, mais il me semble un peu contraire à la cohésion de la classe Student
)
Class Student { /* */ }
Class DB {
public bool AddStudent(Student s)
{
}
public Student RetrieveStudent(Criteria)
{
}
public List<Student> RetrieveAllStudents()
{
}
}
Que diriez-vous de les mélanger?
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
}
}
Principe de responsabilité unique , Séparation des préoccupations et Cohésion fonctionnelle . Si vous lisez ces concepts, la réponse que vous obtenez est: Séparez-les .
Une raison simple de séparer le Student
de la classe "DB" (ou StudentRepository
, pour suivre les conventions les plus courantes) est de vous permettre de modifier vos "règles métier", présentes dans le Student
classe, sans affecter le code responsable de la persistance, et vice-versa.
Ce type de séparation est très important, non seulement entre les règles métier et la persistance, mais entre les nombreuses préoccupations de votre système, pour vous permettre d'apporter des modifications avec un impact minimal dans les modules non liés (minime car parfois inévitable). Il aide à construire des systèmes plus robustes, plus faciles à entretenir et plus fiables en cas de changements constants.
En mélangeant les règles métier et la persistance, soit une seule classe comme dans votre premier exemple, soit avec DB
comme dépendance de Student
, vous associez deux préoccupations très différentes. Il peut sembler qu'ils appartiennent ensemble; ils semblent cohérents car ils utilisent les mêmes données. Mais voici la chose: la cohésion ne peut pas être mesurée uniquement par les données partagées entre les procédures, vous devez également considérer le niveau d'abstraction auquel elles existent. En fait, le type de cohésion idéal est décrit comme:
Cohésion fonctionnelle est lorsque des parties d'un module sont regroupées car elles contribuent toutes à une seule tâche bien définie du module.
Et clairement, effectuer des validations sur un Student
tout en le persistant ne forme pas "une seule tâche bien définie". Encore une fois, les règles métier et les mécanismes de persistance sont deux aspects très différents du système qui, selon de nombreux principes de bonne conception orientée objet, doivent être séparés.
Je recommande de lire Architecture propre , de regarder cela parle du principe de responsabilité unique (où un exemple très similaire est utilisé), et de regarder cette discussion sur l'architecture propre aussi. Ces concepts décrivent les raisons de ces séparations.
Les deux approches violent le principe de responsabilité unique. Votre première version donne à la classe Student
de nombreuses responsabilités et la lie à une technologie d'accès DB spécifique. La seconde mène à une énorme classe DB
qui sera responsable non seulement des étudiants, mais de tout autre type d'objet de données dans votre programme. EDIT: votre troisième approche est la pire, car elle crée une dépendance cyclique entre la classe DB et la classe Student
.
Donc, si vous n'écrivez pas un programme de jouets, n'utilisez aucun d'entre eux. À la place, utilisez une classe différente comme StudentRepository
pour fournir une API de chargement et d'enregistrement, en supposant que vous allez implémenter le code CRUD par vous-même. Vous pouvez également envisager d'utiliser un framework ORM , qui peut faire le travail dur pour vous (et le framework appliquera généralement certaines décisions où les opérations de chargement et d'enregistrement doivent être placés).
Il existe plusieurs modèles pouvant être utilisés pour la persistance des données. Il y a le modèle nité de travail , il y a le modèle Référentiel , il y a quelques modèles supplémentaires qui peuvent être utilisés comme la façade distante, et ainsi de suite.
La plupart d'entre eux ont leurs fans et leurs critiques. Souvent, il s'agit de choisir ce qui semble convenir le mieux à l'application et de s'y tenir (avec tous ses avantages et ses inconvénients, c'est-à-dire de ne pas utiliser les deux modèles en même temps ... sauf si vous en êtes vraiment sûr).
En guise de remarque: dans votre exemple, RetrieveStudent, AddStudent doivent être des méthodes statiques (car elles ne dépendent pas de l'instance).
Une autre façon d'avoir des méthodes de sauvegarde/chargement en classe est:
class Animal{
public string Name {get; set;}
public void Save(){...}
public static Animal Load(string name){....}
public static string[] GetAllNames(){...} // if you just want to list them
public static Animal[] GetAllAnimals(){...} // if you actually need to retrieve them all
}
Personnellement, je n'utiliserais une telle approche que dans des applications assez petites, peut-être des outils à usage personnel ou où je peux prédire de manière fiable que je n'aurai pas de cas d'utilisation plus compliqués que la simple sauvegarde ou le chargement d'objets.
Personnellement, voir le modèle d'unité de travail. Lorsque vous apprenez à le connaître, c'est vraiment bon dans les petits et les grands cas. Et il est supporté par de nombreux frameworks/apis, pour nommer EntityFramework ou RavenDB par exemple.
S'il s'agit d'une application très simple où l'objet est plus ou moins lié au magasin de données et vice-versa (c'est-à-dire qu'il peut être considéré comme une propriété du magasin de données), alors avoir une méthode .save () pour cette classe pourrait avoir un sens.
Mais je pense que ce serait assez exceptionnel.
Au lieu de cela, il est généralement préférable de laisser la classe gérer ses données et ses fonctionnalités (comme un bon citoyen OO), et externaliser le mécanisme de persistance dans une autre classe ou un ensemble de classes.
L'autre option consiste à utiliser un framework de persistance qui définit la persistance de manière déclarative (comme avec les annotations), mais cela continue d'externaliser la persistance.