63 votes

Principe de responsabilité unique vs anti-modèle de domaine anémique

Je participe à un projet qui prend très au sérieux le principe de la Responsabilité Unique. Nous avons beaucoup de petites classes et les choses sont assez simples. Cependant, nous avons un modèle de domaine anémique - il n'y a pas de comportement dans aucune de nos classes de modèle, ce ne sont que des sacs de propriétés. Ce n'est pas une plainte concernant notre conception - en fait, cela semble fonctionner assez bien

Lors de la revue de conception, la Responsabilité Unique est souvent évoquée chaque fois qu'un nouveau comportement est ajouté au système, et donc le nouveau comportement se retrouve généralement dans une nouvelle classe. Cela rend les choses très facilement testables unitairement, mais je suis parfois perplexe car cela semble signifier le retrait du comportement de l'endroit où il est pertinent.

J'essaie d'améliorer ma compréhension de la manière d'appliquer correctement la Responsabilité Unique. Il me semble que la Responsabilité Unique s'oppose à l'ajout de comportements modélisant les affaires qui partagent le même contexte dans un objet, car l'objet finit inévitablement par faire plus d'une chose liée, ou faisant une chose mais connaissant plusieurs règles métier qui modifient la forme de ses sorties.

Si c'est le cas, il semble que le résultat final soit un Modèle de Domaine Anémique, ce qui est certainement le cas dans notre projet. Pourtant, le Modèle de Domaine Anémique est un anti-patron.

Ces deux idées peuvent-elles coexister?

EDIT: Quelques liens liés au contexte:

SRP - http://www.objectmentor.com/resources/articles/srp.pdf
Modèle de Domaine Anémique - http://martinfowler.com/bliki/AnemicDomainModel.html

Je ne suis pas le genre de développeur qui aime juste trouver un prophète et suivre ce qu'il dit comme une vérité absolue. Donc je ne fournis pas ces liens comme un moyen de dire "ce sont les règles", juste comme une source de définition des deux concepts.

0 votes

Avez-vous beaucoup appris depuis que vous avez posté ceci? J'ai quelques années d'expérience en développement et je suis dans une situation similaire à celle que vous avez décrite, où je vois que le projet montre des signes du modèle de domaine anémique, mais je ne sais pas comment équilibrer OOD avec SRP.

0 votes

Wow, cela fait un moment. Je pense que cela reflète où OO en est arrivé - bien loin de ses débuts. À l'université (il y a des années), j'ai appris que OO consistait à encapsuler des données avec des actions liées dans des classes, et le partage du comportement via l'héritage. Maintenant, il s'agit de classes de données légères avec d'autres classes pour agir sur elles, liées ensemble via des motifs de conception et de l'injection de dépendances, et l'héritage est un mot de quatre lettres. Le SRP fonctionne beaucoup mieux avec ce dernier style de OO que le précédent.

0 votes

Que ai-je appris? L'idée originale de modéliser les entités en tant que classes riches peut conduire à un désordre de hiérarchies et de trous percés dans l'encapsulation pour permettre au modèle de se fléchir de nouvelles manières. Cela ne fonctionne pas bien avec le TDD, qui a en partie conduit au SRP et au DI. Dans l'ensemble, c'est bien. Nous avons perdu le fait d'avoir des endroits intuitifs dans le code où vous pouvez rechercher comment votre système fait fonctionner certaines fonctionnalités. Maintenant, vous devez connaître toutes les classes d'acteurs impliquées ou parcourir la base de code. Nous nous appuyons sur les IDE pour trouver des utilisations, des implémentations d'interfaces, etc. Ce sont en partie des moyens de faire face à ce que le SRP et le DI nous ont apporté.

14voto

Cornel Masson Points 439

Le modèle de domaine riche (RDM) et le principe de responsabilité unique (SRP) ne sont pas nécessairement en opposition. Le RDM est plus en désaccord avec une sous-classe très spécialisée de SRP - le modèle préconisant "beans de données + toute la logique métier dans les classes de contrôleur" (DBABLICC).

Si vous lisez le chapitre de Martin sur SRP, vous verrez que son exemple de modem est entièrement dans la couche de domaine, mais abstrait les concepts de DataChannel et Connection en tant que classes séparées. Il garde le Modem lui-même en tant qu'enrobage, car c'est une abstraction utile pour le code client. C'est bien plus une question de bon (re)factoring que de simple stratification. La cohésion et le couplage restent les principes de base de la conception.

Enfin, trois problèmes :

  • Comme Martin le note lui-même, il n'est pas toujours facile de distinguer les différentes 'raisons de changement'. Les concepts de YAGNI, Agile, etc. découragent la prévision de raisons futures de changement, donc nous ne devrions pas en inventer là où elles ne sont pas immédiatement évidentes. Je vois les 'raisons de changement prématurées et anticipées' comme un véritable risque dans l'application de SRP et cela devrait être géré par le développeur.

  • En plus du précédent, même une application correcte (mais inutilement pointilleuse) de SRP peut entraîner une complexité non désirée. Pensez toujours au pauvre diable qui devra maintenir votre classe : est-ce que l'abstraction diligente d'un comportement trivial en ses propres interfaces, classes de base et implémentations d'une ligne va vraiment faciliter sa compréhension de ce qui aurait simplement dû être une seule classe?

  • La conception de logiciels consiste souvent à trouver le meilleur compromis entre des forces concurrentes. Par exemple, une architecture en couches est en grande partie une bonne application de SRP, mais que dire du fait que, par exemple, le changement d'une propriété d'une classe métier de, disons, un booléen à un enum a un effet domino sur toutes les couches - de la base de données à la couche de domaine, en passant par les façades, les services web, jusqu'à l'interface utilisateur? Cela indique-t-il une mauvaise conception? Pas nécessairement : cela montre que votre conception favorise un aspect du changement par rapport à un autre.

2 votes

"DBABLICC" n'est pas vraiment une forme de SRP mais plutôt une manière pour les programmeurs procéduraux de rationaliser leur programmation procédurale en utilisant un langage orienté objet.

9voto

CPerkins Points 5209

Je dirais "oui", mais vous devez bien faire votre SRP. Si la même opération s'applique à une seule classe, elle doit figurer dans cette classe, n'est-ce pas? Et si la même opération s'applique à plusieurs classes? Dans ce cas, si vous voulez suivre le modèle OO de combiner données et comportements, vous placerez l'opération dans une classe de base, n'est-ce pas?

Je soupçonne que d'après votre description, vous finissez avec des classes qui sont essentiellement des sacs d'opérations, et vous avez essentiellement recréé le style de codage C: structs et modules.

De l'article SRP lié: "Le SRP est l'un des principes les plus simples et l'un des plus difficiles à mettre en œuvre correctement."

3 votes

Hmm... J'aimerais être convaincu, mais je ne le suis pas. Privilégiez la composition sur l'héritage va à l'encontre de votre argument...

2 votes

D'accord, nous pouvons échanger des citations bibliques, ou débattre de savoir si "la faveur" nécessite l'absolutisme. Ou bien nous pouvons essayer de résoudre le problème de l'auteur du post. Quelle est votre opinion sur la manière de résoudre le problème de l'auteur du post ?

1 votes

Merci pour votre réponse. Je trouve ces jours-ci que le modèle OO de combiner les données et le comportement est très démodé. Je suis certainement conscient des pièges qui peuvent accompagner les hiérarchies d'héritage complexes, mais il semble que l'héritage soit presque un mot tabou de nos jours. Le SRP et la préférence pour la composition plutôt que pour l'héritage semblent aller à l'encontre des modèles de domaine riches. Je pense que les problèmes surviennent lorsque les principes sont suivis parce qu'ils sont des principes, plutôt que parce qu'ils fonctionnent le mieux pour votre projet. Comme vous le dites, des citations des Écritures.

7voto

KeithS Points 81

La citation du document SRP est très juste; il est difficile de bien faire SRP. Celui-ci et OCP sont les deux éléments de SOLID qui doivent simplement être assouplis à un certain degré pour mener à bien un projet. Une application excessive de l'un ou l'autre produira très rapidement du code ravioli.

SRP peut en effet être poussé à des limites ridicules, si les "raisons de changement" sont trop spécifiques. Même un "sac de données" POCO/POJO peut être considéré comme violant SRP, si vous considérez le changement de type d'un champ comme un "changement". On penserait que le bon sens vous dirait que le changement de type d'un champ est une concession nécessaire pour le "changement", mais j'ai vu des couches de domaine avec des wrappers pour des types de valeurs intégrés; un enfer qui rend ADM comme une utopie.

Il est souvent bon de vous ancrer dans un objectif réaliste, basé sur la lisibilité ou un niveau de cohésion souhaité. Lorsque vous dites, "Je veux que cette classe fasse une seule chose", elle ne doit avoir ni plus ni moins que ce qui est nécessaire pour le faire. Vous pouvez maintenir au moins une cohésion procédurale avec cette philosophie de base. "Je veux que cette classe conserve toutes les données pour une facture" permettra généralement QUELQUES logiques métier, même la sommation des sous-totaux ou le calcul de la taxe de vente, basé sur la responsabilité de l'objet de savoir comment vous donner une valeur précise et interne cohérente pour chaque champ qu'il contient.

Personnellement, je n'ai pas de gros problème avec un domaine "léger". Le simple fait d'être l'expert en données fait de l'objet du domaine le gardien de chaque champ/propriété pertinent à la classe, ainsi que de toute la logique des champs calculés, des conversions de type de données explicites/implicites et éventuellement des règles de validation plus simples (c'est-à-dire des champs requis, des limites de valeurs, des éléments qui casseraient l'instance internement si autorisés). Si un algorithme de calcul, peut-être pour une moyenne pondérée ou mobile, est susceptible de changer, encapsulez l'algorithme et référez-vous à celui-ci dans le champ calculé (c'est juste un bon OCP/PV).

Je ne considère pas un tel objet de domaine comme "anémique". Ma perception de ce terme est un "sac de données", une collection de champs qui n'a aucune conception du monde extérieur ou même de la relation entre ses champs autre que le fait qu'il les contient. Je l'ai aussi vu, et ce n'est pas amusant de traquer les incohérences dans l'état de l'objet que l'objet n'a jamais su être un problème. Un SRP excessif y conduira en affirmant qu'un objet de données n'est pas responsable de toute logique métier, mais le bon sens interviendrait généralement d'abord pour dire que l'objet, en tant qu'expert en données, doit être responsable de maintenir un état interne cohérent.

Encore une fois, opinion personnelle, je préfère le pattern Repository à Active Record. Un objet, avec une seule responsabilité, et très peu, voire rien d'autre dans le système au-dessus de cette couche doit savoir comment cela fonctionne. Active Record exige de la couche de domaine de connaître au moins certains détails spécifiques sur la méthode de persistance ou le framework (que ce soit les noms de procédures stockées utilisées pour lire/écrire chaque classe, les références d'objet spécifiques au framework, ou les attributs décorant les champs avec des informations ORM), et injecte ainsi une deuxième raison de changement dans chaque classe de domaine par défaut.

Mon point de vue.

0 votes

Je pense que votre commentaire vers la fin selon lequel l'objet doit être responsable de maintenir un état interne cohérent est le véritable argument final. Les modèles anémiques que j'ai vus laissent cette responsabilité aux classes qui manipulent le modèle via des setters publics, en se basant sur l'idée que le calcul d'une valeur est une responsabilité distincte du fait de fournir la valeur, quelle que soit la complexité du calcul.

5voto

Saem Points 2415

J'ai constaté que suivre les principes SOLID m'a effectivement éloigné du modèle de domaine riche de DDD, mais au final, je me suis rendu compte que cela m'importait peu. Plus précisément, j'ai remarqué que le concept logique d'un modèle de domaine et une classe dans n'importe quel langage n'étaient pas nécessairement associés de manière 1:1, sauf s'il s'agissait d'une façade de quelque sorte.

Je ne dirais pas que c'est exactement un style de programmation en C où vous avez des structures et des modules, mais plutôt vous finirez probablement avec quelque chose de plus fonctionnel. Je me rends compte que les styles sont similaires, mais les détails font une grande différence. J'ai remarqué que mes instances de classe finissent par se comporter comme des fonctions d'ordre supérieur, des applications de fonctions partielles, des fonctions évaluées de manière paresseuse, ou une combinaison des deux. C'est un peu ineffable pour moi, mais c'est le sentiment que j'obtiens en écrivant du code en suivant TDD + SOLID, cela finit par se comporter comme un style hybride OO/Functional.

Quant à l'héritage étant un mot tabou, je pense que cela est dû au fait que l'héritage n'est pas suffisamment finement granulaire dans des langages comme Java/C#. Dans d'autres langages, c'est moins un problème et plus utile.

1voto

JontyMC Points 1156

J'aime la définition de SRP comme suit:

"Une classe n'a qu'une seule raison professionnelle de changer"

Donc, tant que les comportements peuvent être regroupés en une seule "raison professionnelle", il n'y a aucune raison pour qu'ils ne coexistent pas dans la même classe. Bien sûr, ce qui définit une "raison professionnelle" est sujet à débat (et doit être débattu par toutes les parties prenantes).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X