Ceci est une transcription plus bien formée de mon commentaire initial sous votre question. Les réponses aux questions posées par le PO se trouvent au bas de cette réponse. Veuillez également vérifier la note importante située au même endroit.
Ce que vous décrivez actuellement, Sipo, est un modèle de conception appelé enregistrement actif . Comme pour tout, même celui-ci a trouvé sa place parmi les programmeurs, mais a été rejeté au profit du référentiel et des modèles de mappeur de données pour une raison simple, l'évolutivité.
En bref, un enregistrement actif est un objet qui:
- représente un objet dans votre domaine (comprend des règles métier, sait comment gérer certaines opérations sur l'objet, par exemple si vous pouvez ou ne pouvez pas changer un nom d'utilisateur, etc.),
- sait comment récupérer, mettre à jour, enregistrer et supprimer l'entité.
Vous abordez plusieurs problèmes avec votre conception actuelle et le problème principal de votre conception est résolu au dernier, 6ème point (dernier mais non le moindre, je suppose). Lorsque vous avez une classe pour laquelle vous concevez un constructeur et que vous ne savez même pas ce que le constructeur doit faire, la classe fait probablement quelque chose de mal. C'est arrivé dans votre cas.
Mais la fixation de la conception est en fait assez simple en divisant la représentation d'entité et la logique CRUD en deux (ou plus) classes.
Voici à quoi ressemble votre design maintenant:
Employee
- contient des informations sur la structure des employés (ses attributs) et les méthodes de modification de l'entité (si vous décidez de suivre la voie mutable), contient la logique CRUD pour l' Employee
entité, peut renvoyer une liste d' Employee
objets, accepte un Employee
objet lorsque vous le souhaitez mettre à jour un employé, peut renvoyer un seul Employee
par une méthode commegetSingleById(id : string) : Employee
Wow, la classe semble énorme.
Ce sera la solution proposée:
Employee
- contient des informations sur la structure des employés (ses attributs) et les méthodes de modification de l'entité (si vous décidez de suivre la voie mutable)
EmployeeRepository
- contient la logique CRUD pour l' Employee
entité, peut renvoyer une liste d' Employee
objets, accepte un Employee
objet lorsque vous souhaitez mettre à jour un employé, peut renvoyer un seul Employee
via une méthode commegetSingleById(id : string) : Employee
Avez-vous entendu parler de la séparation des préoccupations ? Non, vous le ferez maintenant. C'est la version moins stricte du principe de responsabilité unique, qui dit qu'une classe ne devrait en fait avoir qu'une seule responsabilité, ou comme le dit l'oncle Bob:
Un module doit avoir une et une seule raison de changer.
Il est tout à fait clair que si je pouvais diviser clairement votre classe initiale en deux qui ont toujours une interface bien arrondie, la classe initiale en faisait probablement trop, et c'était le cas.
Ce qui est génial avec le modèle de référentiel, il agit non seulement comme une abstraction pour fournir une couche intermédiaire entre les bases de données (qui peut être n'importe quoi, fichier, noSQL, SQL, orienté objet), mais il n'a même pas besoin d'être concret classe. Dans de nombreux langages OO, vous pouvez définir l'interface comme une réelle interface
(ou une classe avec une méthode virtuelle pure si vous êtes en C ++), puis avoir plusieurs implémentations.
Cela lève complètement la décision de savoir si un référentiel est une implémentation réelle de vous reposez simplement sur l'interface en vous appuyant réellement sur une structure avec le interface
mot - clé. Et le référentiel est exactement cela, c'est un terme de fantaisie pour l'abstraction de la couche de données, à savoir le mappage des données à votre domaine et vice versa.
Une autre grande chose à propos de la séparer en (au moins) deux classes est que maintenant la Employee
classe peut clairement gérer ses propres données et le faire très bien, car elle n'a pas besoin de s'occuper d'autres choses difficiles.
Question 6: Que doit donc faire le constructeur dans la Employee
classe nouvellement créée ? C'est simple. Il doit prendre les arguments, vérifier s'ils sont valides (comme un âge ne doit probablement pas être négatif ou un nom ne doit pas être vide), déclencher une erreur lorsque les données ne sont pas valides et si la validation réussie attribue les arguments à des variables privées de l'entité. Il ne peut plus communiquer avec la base de données, car il ne sait tout simplement pas comment procéder.
Question 4: Pas de réponse du tout, pas de manière générale, car la réponse dépend fortement de ce dont vous avez besoin.
Question 5: Maintenant que vous avez séparé la classe pléthorique en deux, vous pouvez avoir plusieurs méthodes de mise à jour directement sur la Employee
classe, comme changeUsername
, markAsDeceased
qui manipulera les données de la Employee
classe dans la RAM et vous pouvez alors introduire une méthode telle que registerDirty
de la Modèle d' unité de travail à la classe de référentiel, à travers lequel vous indiquerez au référentiel que cet objet a changé de propriétés et devra être mis à jour après avoir appelé la commit
méthode.
De toute évidence, pour une mise à jour, un objet nécessite d'avoir un identifiant et donc d'être déjà enregistré, et c'est la responsabilité du référentiel de le détecter et de générer une erreur lorsque les critères ne sont pas remplis.
Question 3: Si vous décidez de suivre le modèle d'unité de travail, la create
méthode sera désormais registerNew
. Si vous ne le faites pas, je l'appellerais probablement à la save
place. Le but d'un référentiel est de fournir une abstraction entre le domaine et la couche de données, pour cette raison, je vous recommande que cette méthode (que ce soit registerNew
ou save
) accepte l' Employee
objet et qu'elle appartient aux classes implémentant l'interface du référentiel, qui attribue ils décident de sortir de l'entité. Il est préférable de passer un objet entier, vous n'avez donc pas besoin d'avoir de nombreux paramètres facultatifs.
Question 2: Les deux méthodes feront désormais partie de l'interface du référentiel et ne violent pas le principe de responsabilité unique. La responsabilité du référentiel est de fournir des opérations CRUD pour les Employee
objets, c'est ce qu'il fait (en plus de Lire et Supprimer, CRUD se traduit à la fois par Créer et Mettre à jour). Évidemment, vous pouvez diviser encore plus le référentiel en ayant un EmployeeUpdateRepository
et ainsi de suite, mais cela est rarement nécessaire et une seule implémentation peut généralement contenir toutes les opérations CRUD.
Question 1: Vous vous êtes retrouvé avec une Employee
classe simple qui aura désormais (entre autres attributs) l'identifiant. Que l'id soit rempli ou vide (ou null
) dépend de si l'objet a déjà été enregistré. Néanmoins, un id est toujours un attribut que possède l'entité et la responsabilité de l' Employee
entité est de prendre soin de ses attributs, donc de prendre soin de son id.
Qu'une entité ait ou non un identifiant n'a généralement pas d'importance jusqu'à ce que vous essayiez de faire une logique de persistance sur elle. Comme mentionné dans la réponse à la question 5, il est de la responsabilité du référentiel de détecter que vous n'essayez pas de sauvegarder une entité qui a déjà été enregistrée ou que vous essayez de mettre à jour une entité sans identifiant.
Note importante
Veuillez noter que bien que la séparation des préoccupations soit grande, la conception d'une couche de référentiel fonctionnel est un travail assez fastidieux et, selon mon expérience, est un peu plus difficile à bien comprendre que l'approche d'enregistrement actif. Mais vous vous retrouverez avec un design beaucoup plus flexible et évolutif, ce qui peut être une bonne chose.
Employee
objet pour fournir l'abstraction, les questions 4. et 5. sont généralement sans réponse, dépendent de vos besoins, et si vous séparez la structure et les opérations CRUD en deux classes, alors c'est assez clair, le constructeur duEmployee
ne peut pas récupérer les données de db plus, donc ça répond 6.