Anti-modèle du principe de la responsabilité unique vs du 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 aucun comportement dans nos classes de modèles, ce ne sont que des sacs de propriété. Ce n’est pas une plainte à propos de notre conception – il semble en fait très bien fonctionner

Au cours des révisions de conception, SRP est mis en évidence chaque fois qu’un nouveau comportement est ajouté au système, ce qui entraîne généralement l’apparition d’un nouveau comportement dans une nouvelle classe. Cela permet de garder les choses très facilement à l’épreuve des unités, mais je suis parfois perplexe parce que cela donne l’impression de retirer le comportement de l’endroit où il est pertinent.

J’essaie d’améliorer ma compréhension de la façon d’appliquer correctement SRP. Il me semble que SRP s’oppose à l’ajout d’un comportement de modélisation métier qui partage le même contexte à un object, car l’object finit inévitablement par faire plus d’une chose, ou faire une chose, mais en connaissant plusieurs règles métier qui modifient la forme. de ses sorties.

Si tel est le cas, alors le résultat final est 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-modèle.

Ces deux idées peuvent-elles coexister?

EDIT: Quelques liens contextuels:

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 évangile. Je ne propose donc pas de liens vers ces termes pour affirmer que «ce sont les règles», juste comme source de définition des deux concepts.

    Je dois dire “oui”, mais vous devez faire correctement votre SRP. Si la même opération ne s’applique qu’à une seule classe, elle appartient à cette classe, n’est-ce pas? Que diriez-vous si la même opération s’applique à plusieurs classes? Dans ce cas, si vous souhaitez suivre le modèle OO de combinaison de données et de comportement, vous devez placer l’opération dans une classe de base, non?

    Je pense que d’après votre description, vous vous retrouvez avec des classes qui sont essentiellement des sacs d’opérations, vous avez donc essentiellement recréé le style C du codage: structures et modules.

    D’après le document SRP lié: ” Le SRP est l’un des principes les plus simples et l’un des plus difficiles à corriger.

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

    Si vous lisez le chapitre SRP de Martin, vous verrez que son exemple de modem se trouve entièrement dans la couche de domaine, mais que les concepts DataChannel et Connection sont abstraits en tant que classes distinctes. Il garde le Modem lui-même en tant que wrapper, car il s’agit d’une abstraction utile pour le code client. Il s’agit beaucoup plus de (re) factoring que de simple superposition . La cohésion et le couplage restnt les principes de base du design.

    Enfin, trois numéros:

    • Comme Martin le note lui-même, il n’est pas toujours facile de voir les différentes «raisons du changement». Les concepts mêmes de YAGNI, Agile, etc. découragent l’anticipation des raisons futures du changement, il ne faut donc pas en inventer où ils ne sont pas immédiatement évidents. Je vois «des raisons de changement anticipées et anticipées» comme un risque réel dans l’application de SRP et devrait être gérée par le développeur.

    • Suite à la précédente, même une application correcte (mais inutile) de la SRP peut entraîner une complexité indésirable. Pensez toujours au prochain gazon qui doit maintenir votre classe: l’abstraction diligente du comportement sortingvial dans ses propres interfaces, classes de base et implémentations sur une seule ligne, aidera-t-il vraiment à comprendre ce qui aurait dû être une classe unique?

    • La conception de logiciels consiste souvent à obtenir le meilleur compromis entre des forces concurrentes. Par exemple, une architecture en couches est principalement une bonne application de SRP, mais qu’en est-il du fait que, par exemple, le changement d’une propriété d’une classe métier d’un booléen à un enum a un effet d’entraînement sur tous les calques? – de la firebase database au domaine, aux façades, au service Web, à l’interface graphique? Cela signifie-t-il une mauvaise conception? Pas nécessairement: cela montre que votre conception privilégie un aspect du changement à un autre.

    La citation du papier SRP est très correcte; SRP est difficile à comprendre. Celui-ci et OCP sont les deux éléments de SOLID qui doivent simplement être assouplis au moins dans une certaine mesure afin de réaliser un projet. L’application trop zélée de l’une ou l’autre produira très rapidement du code ravioli.

    La SRP peut en effet être scope à des longueurs ridicules, si les “raisons du changement” sont trop spécifiques. Même un “sac de données” POCO / POJO peut être considéré comme une violation du SRP, si vous considérez que le type d’un champ change comme un “changement”. Vous penseriez que le bon sens vous dirait que le changement de type d’un champ est nécessaire pour “changer”, mais j’ai vu des couches de domaine avec des wrappers pour les types de valeur intégrés; un enfer qui fait ressembler ADM à l’utopie.

    Il est souvent bon de se fixer un objective réaliste, basé sur la lisibilité ou le niveau de cohésion souhaité. Quand vous dites: “Je veux que cette classe fasse une chose”, elle ne devrait pas avoir plus ou moins que ce qui est nécessaire pour le faire. Vous pouvez maintenir au moins la cohésion procédurale avec cette philosophie de base. “Je souhaite que cette classe conserve toutes les données d’une facture” permet généralement à la logique métier SOME, même de calculer des sous-totaux ou de calculer la taxe de vente, en fonction de la responsabilité de l’object 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’object de domaine le gardien de chaque champ / propriété pertinent pour la classe, ainsi que toute logique de champ calculée, toute conversion de type de données explicite / implicite et les règles de validation plus simples. (c.-à-d. les champs obligatoires, les limites de valeur, les choses qui casseraient l’instance en interne si cela est autorisé). Si un algorithme de calcul, peut-être pour une moyenne pondérée ou moyenne, est susceptible de changer, encapsulez l’algorithme et faites-y référence dans le champ calculé (c’est juste un bon OCP / PV).

    Je ne considère pas qu’un tel object de domaine soit “anémique”. Ma perception de ce terme est un “sac de données”, un ensemble de champs qui n’a aucun concept du monde extérieur ni même la relation entre ses champs autres que ceux qui les contiennent. J’ai aussi vu ça, et ce n’est pas amusant de retrouver des incohérences dans l’état d’object que l’object n’a jamais connu était un problème. Une surexplication SRP entraînera ceci en déclarant qu’un object de données n’est responsable d’aucune logique métier, mais le bon sens intervient généralement en premier et dit que l’object, en tant qu’expert en données, doit être responsable du maintien d’un état interne cohérent.

    Encore une fois, mon opinion personnelle, je préfère le modèle de référentiel à Active Record. Un object, avec une seule responsabilité, et très peu, sinon rien, dans le système au-dessus de cette couche, doit savoir quelque chose sur son fonctionnement. Active Record nécessite que la couche de domaine connaisse au moins quelques détails spécifiques sur la méthode ou le framework de persistance (qu’il s’agisse des noms des procédures stockées utilisées pour lire / écrire chaque classe, des références d’objects spécifiques à la structure ou des atsortingbuts décorant les champs avec des informations ORM) ), et injecte ainsi une deuxième raison de changer par défaut dans chaque classe de domaine.

    Mon $ 0.02.

    J’ai trouvé que suivre les principes solides m’avait en fait éloigné du modèle de domaine riche de DDD, et finalement, je m’en fichais. Plus précisément, j’ai trouvé que le concept logique d’un modèle de domaine, et une classe dans un langage quelconque, n’étaient pas mappés 1: 1, à moins que nous ne parlions d’une façade quelconque.

    Je ne dirais pas que c’est exactement un c-style de programmation où vous avez des structures et des modules, mais vous allez probablement vous retrouver 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 trouvé que mes instances de classe se comportaient comme des fonctions d’ordre supérieur, des applications de fonctions partielles, des fonctions évaluées paresseusement ou une combinaison des fonctions ci-dessus. C’est un peu ineffable pour moi, mais c’est le sentiment que j’ai en écrivant du code après TDD + SOLID, ça a fini par se comporter comme un style hybride OO / Functional.

    En ce qui concerne l’inheritance étant un mauvais mot, je pense que c’est plus dû au fait que l’inheritance n’est pas suffisamment fin dans les langages tels que Java / C #. Dans d’autres langues, c’est moins problématique et plus utile.

    J’aime la définition de SRP comme:

    “Une classe n’a qu’une seule raison de changer d’entreprise”

    Donc, tant que les comportements peuvent être regroupés dans des «raisons commerciales» uniques, il n’y a aucune raison pour qu’ils ne coexistent pas dans la même classe. Bien entendu, ce qui définit une “raison commerciale” est ouvert au débat (et devrait être débattu par toutes les parties prenantes).

    Avant d’entrer dans ma gueule, voici mon opinion en un mot: quelque part tout doit se rejoindre … et puis une rivière la traverse.

    Je suis hanté par le codage.

    =======

    Modèle de données anémiques et moi … eh bien, nous sums beaucoup dans le coin. Peut-être est-ce juste la nature des petites et moyennes applications avec très peu de logique métier intégrée. Je suis peut-être un peu en retard.

    Cependant, voici mes 2 cents:

    Ne pourriez-vous pas simplement factoriser le code dans les entités et le relier à une interface?

    public class Object1 { public ssortingng Property1 { get; set; } public ssortingng Property2 { get; set; } private IAction1 action1; public Object1(IAction1 action1) { this.action1 = action1; } public void DoAction1() { action1.Do(Property1); } } public interface IAction1 { void Do(ssortingng input1); } 

    Est-ce que cela va à l’encontre des principes de la SRP?

    De plus, ne pas avoir un tas de classes assises autour ne soit pas liées les unes aux autres par le code de consommation en fait une violation plus grande de SRP, mais poussé une couche?

    Imaginez le gars qui écrit le code client assis là pour essayer de comprendre comment faire quelque chose lié à Object1. S’il doit travailler avec votre modèle, il travaillera avec Object1, le sac de données et un ensemble de «services» chacun ayant une responsabilité unique. Ce sera son travail de s’assurer que toutes ces choses interagissent correctement. Donc, maintenant, son code devient un script de transaction et ce script contiendra lui-même toutes les responsabilités nécessaires pour effectuer correctement cette transaction (ou unité de travail) particulière.

    De plus, vous pourriez dire: “no brah, tout ce qu’il a à faire est d’accéder à la couche service. C’est comme Object1Service.DoActionX (Object1). Un morceau de gâteau.” Eh bien, où est la logique maintenant? Tout en une méthode? Votre tout simplement pousser le code autour, et peu importe, vous vous retrouverez avec des données et la logique étant séparés.

    Donc, dans ce scénario, pourquoi ne pas exposer au code client cet Object1Service particulier et que DoActionX () soit simplement un autre hook pour votre modèle de domaine? Je veux dire par là:

     public class Object1Service { private Object1Repository repository; public Object1Service(Object1Repository repository) { this.repository = repository; } // Tie in your Unit of Work Aspect'ing stuff or whatever if need be public void DoAction1(Object1DTO object1DTO) { Object1 object1 = repository.GetById(object1DTO.Id); object1.DoAction1(); repository.Save(object1); } } 

    Vous avez toujours pris en compte le code réel pour Action1 de Object1, mais pour tous les besoins intensifs, ayez un Object1 non anémique.

    Supposons que vous ayez besoin d’Action1 pour représenter 2 (ou plus) opérations différentes que vous souhaiteriez faire atomiques et séparer dans leurs propres classes. Créez simplement une interface pour chaque opération atomique et connectez-la à l’intérieur de DoAction1.

    C’est comme ça que je pourrais aborder cette situation. Mais là encore, je ne sais pas vraiment ce que c’est que SRP.

    Convertissez vos objects de domaine brut en modèle ActiveRecord avec une classe de base commune à tous les objects du domaine. Mettez le comportement commun dans la classe de base et remplacez le comportement dans les classes dérivées chaque fois que cela est nécessaire ou définissez le nouveau comportement, le cas échéant.