Le principe de responsabilité unique stipule qu'une classe doit faire une et une seule chose. Certains cas sont assez clairs. D'autres, cependant, sont difficiles car ce qui ressemble à «une chose» vu à un niveau d'abstraction donné peut être multiple si on le regarde à un niveau inférieur. Je crains également que, si le principe de responsabilité unique soit respecté aux niveaux inférieurs, il en résulte un code de raviole verbeux excessivement découplé, où plus de lignes sont consacrées à la création de petites classes pour tout et à la plomberie d’informations plutôt qu’à la résolution du problème actuel.
Comment décririez-vous ce que "une chose" signifie? Quels sont certains signes concrets qu'une classe fait plus que "une chose"?
design
object-oriented
Dsimcha
la source
la source
Réponses:
J'aime beaucoup la manière dont Robert C. Martin (Oncle Bob) réaffirme le principe de responsabilité unique (lié au PDF) :
C'est une définition légèrement différente de la définition traditionnelle du «ne devrait faire qu'une chose» et j'aime bien cela parce que cela vous oblige à changer votre façon de penser de votre classe. Au lieu de penser à "est-ce que cela fait une chose?", Vous pensez plutôt à ce qui peut changer et à la manière dont ces changements affectent votre classe. Ainsi, par exemple, si la base de données change, votre classe doit-elle changer? Qu'en est-il si le périphérique de sortie change (par exemple, un écran, un périphérique mobile ou une imprimante)? Si votre classe doit changer en raison de changements venant de nombreuses autres directions, c'est un signe que votre classe a trop de responsabilités.
Dans l'article lié, Oncle Bob conclut:
la source
Je n'arrête pas de me demander quel est le problème que SRP tente de résoudre. Quand SRP m'aide-t-il? Voici ce que je suis venu avec:
Vous devez repenser la responsabilité / fonctionnalité en dehors de la classe lorsque:
1) Vous avez dupliqué la fonctionnalité (DRY)
2) Vous trouvez que votre code a besoin d'un autre niveau d'abstraction pour vous aider à le comprendre (KISS)
3) Vous constatez que vos experts en matière de domaine considèrent que les fonctionnalités sont un composant distinct (le langage omniprésent)
Vous NE DEVRIEZ PAS repenser la responsabilité d'une classe lorsque:
1) Il n'y a pas de fonctionnalité dupliquée.
2) La fonctionnalité n'a pas de sens en dehors du contexte de votre classe. En d'autres termes, votre classe fournit un contexte dans lequel il est plus facile de comprendre la fonctionnalité.
3) Les experts de votre domaine n’ont aucune idée de cette responsabilité.
Je me rends compte que si SRP s’applique au sens large, nous négocions un type de complexité (essayer de faire taire une classe avec beaucoup trop de choses à l’intérieur) avec un autre type (essayer de garder tous les collaborateurs / niveaux de l’abstraction pour comprendre ce que font réellement ces classes).
En cas de doute, laissez tomber! Vous pouvez toujours refacturer plus tard, quand il y a un cas clair pour cela.
Qu'est-ce que tu penses?
la source
Je ne sais pas s'il y a une échelle objective, mais ce qui donnerait cela serait les méthodes - pas tellement leur nombre, mais la variété de leurs fonctions. Je conviens que vous pouvez aller trop loin dans la décomposition mais je ne suivrais aucune règle stricte à ce sujet.
la source
La réponse réside dans la définition
Ce que vous définissez comme étant la responsabilité, vous donne finalement la limite.
Exemple:
J'ai un composant qui a la responsabilité d' afficher les factures -> dans ce cas, si je commençais à ajouter quelque chose de plus, je briserais le principe.
Si par contre si je disais la responsabilité de traiter les factures -> en ajoutant plusieurs fonctions plus petites (par exemple, impression de facture , mise à jour de facture ), elles se situent toutes dans cette limite.
Toutefois, si ce module commençait à gérer une fonctionnalité en dehors des factures , il se trouverait en dehors de cette limite.
la source
Je le vois toujours à deux niveaux:
Donc, quelque chose comme un objet de domaine appelé Dog:
Dog
est ma classe mais les chiens peuvent faire beaucoup de choses! Je pourrais avoir des méthodes telles quewalk()
,run()
etbite(DotNetDeveloper spawnOfBill)
(désolé , je n'ai pas pu résister; p).Si cela
Dog
devient trop compliqué, je penserais à la façon dont des groupes de ces méthodes peuvent être modélisés ensemble dans une autre classe, telle qu'uneMovement
classe, qui pourrait contenir mywalk()
etrun()
méthodes.Il n'y a pas de règle absolue, votre conception OO évoluera avec le temps. J'essaie d'avoir une interface publique / interface claire et des méthodes simples qui font bien une chose et une chose.
la source
Je le regarde plus dans les lignes d'une classe ne devrait représenter qu'une chose. Pour m'approprier l'exemple de Karianna, j'ai ma
Dog
classe, qui a des méthodes pourwalk()
,run()
etbark()
. Je ne vais pas ajouter des méthodes pourmeaow()
,squeak()
,slither()
oufly()
parce que ce ne sont pas des choses que les chiens. Ce sont des choses que font d'autres animaux, et ces autres animaux auraient leurs propres classes pour les représenter.(BTW, si votre chien ne voler, alors vous devriez probablement arrêter de le jeter hors de la fenêtre).
la source
SeesFood
comme une caractéristique deDogEyes
,Bark
comme chose faite par unDogVoice
, etEat
comme une chose faite par unDogMouth
, alors la logiqueif (dog.SeesFood) dog.Eat(); else dog.Bark();
devientif (eyes.SeesFood) mouth.Eat(); else voice.Bark();
, perdant tout sens de l'identité que les yeux, la bouche et la voix sont tous connectés à une seule entité.Dog
classe, alors il est probablementDog
lié. Sinon, vous vous retrouveriez probablement avec quelque chose commemyDog.Eyes.SeesFood
plutôt que justeeyes.SeesFood
. Une autre possibilité est queDog
expose l'ISee
interface qui exige laDog.Eyes
propriété et laSeesFood
méthode.Eye
classe, mais un chien doit "voir" en utilisant ses yeux, plutôt que de simplement avoir des yeux qui peuvent voir. Un chien n'est pas un œil, mais ce n'est pas simplement un porte-vue. C'est une "chose qui peut [au moins essayer de] voir", et devrait être décrite comme telle par l'interface. Même un chien aveugle peut être demandé s'il voit de la nourriture; cela ne sera pas très utile, car le chien dira toujours "non", mais demander n'est pas mal.Une classe devrait faire une chose lorsqu'elle est considérée à son propre niveau d'abstraction. Il fera sans doute beaucoup de choses à un niveau moins abstrait. C'est ainsi que les classes travaillent pour rendre les programmes plus faciles à maintenir: elles cachent les détails de l'implémentation si vous n'avez pas besoin de les examiner de près.
J'utilise les noms de classe comme test pour cela. Si je ne peux pas donner à une classe un nom descriptif assez court, ou si ce nom contient un mot comme "Et", je viole probablement le principe de responsabilité unique.
D'après mon expérience, il est plus facile de maintenir ce principe aux niveaux inférieurs, où les choses sont plus concrètes.
la source
Il s'agit d'avoir un rôle unique .
Chaque classe doit être reprise par un nom de rôle. Un rôle est en fait un (ensemble de) verbe (s) associé (s) à un contexte.
Par exemple :
Fichier fournit un accès au fichier. FileManager gère les objets File.
La ressource contient des données pour une ressource d'un fichier. ResourceManager contient et fournit toutes les ressources.
Ici, vous pouvez voir que certains verbes comme "gérer" impliquent un ensemble d'autres verbes. Les verbes seuls sont mieux considérés comme des fonctions que des classes, la plupart du temps. Si le verbe implique trop d'actions ayant leur propre contexte commun, il devrait alors être une classe en soi.
Donc, l’idée est seulement de vous donner une idée simple de ce que fait la classe en définissant un rôle unique, qui pourrait être l’agrégat de plusieurs sous-rôles (joués par des objets membres ou d’autres objets).
Je construis souvent des classes de gestionnaires qui contiennent plusieurs autres classes. Comme une usine, un registre, etc. Voir une classe de gestionnaires comme une sorte de chef de groupe, un chef d'orchestre qui guide les autres peuples à travailler ensemble pour atteindre une idée de haut niveau. Il a un rôle, mais implique de travailler avec d'autres rôles uniques à l'intérieur. Vous pouvez également voir cela de la manière dont une entreprise est organisée: un PDG n’est pas productif sur le plan de la productivité pure, mais s’il n’y est pas, rien ne peut fonctionner correctement ensemble. C'est son rôle.
Lorsque vous concevez, identifiez des rôles uniques. Et pour chaque rôle, voyez à nouveau s'il ne peut pas être coupé dans plusieurs autres rôles. De cette façon, si vous avez besoin de changer simplement la façon dont votre gestionnaire construit les objets, il vous suffit de changer de fabrique et d’avoir la tranquillité à l’esprit.
la source
Le SRP ne consiste pas uniquement à diviser les classes, mais également à déléguer des fonctionnalités.
Dans l'exemple de chien utilisé ci-dessus, n'utilisez pas SRP comme justification pour avoir 3 classes distinctes telles que DogBarker, DogWalker, etc. (faible cohésion). Au lieu de cela, regardez l'implémentation des méthodes d'une classe et décidez si elles "en savent trop". Vous pouvez toujours avoir dog.walk (), mais la méthode walk () devrait probablement déléguer à une autre classe les détails de la marche à suivre.
En fait, nous permettons à la classe Dog d’avoir une raison de changer: parce que les chiens changent. Bien sûr, en combinant cela avec d'autres principes SOLID, vous étendez Extend Dog pour de nouvelles fonctionnalités plutôt que de changer Dog (ouvert / fermé). Et vous injecteriez vos dépendances telles que IMove et IEat. Et bien sûr, vous feriez ces interfaces séparées (séparation des interfaces). Chien ne changerait que si nous trouvions un bug ou si les chiens étaient fondamentalement modifiés (Liskov Sub, ne prolongez pas et supprimez le comportement).
SOLID a pour effet net d’écrire un nouveau code plus souvent que de modifier le code existant, ce qui est une grande victoire.
la source
Tout dépend de la définition de responsabilité et de l'impact de cette définition sur la maintenance de votre code. Tout se résume à une chose et c'est ainsi que votre conception va vous aider à gérer votre code.
Et comme quelqu'un l'a dit, "marcher sur l'eau et concevoir un logiciel à partir de spécifications données est facile, à condition que les deux soient gelés".
Donc, si nous définissons la responsabilité de manière plus concrète, nous n’avons pas besoin de la changer.
Parfois, les responsabilités sont évidentes, mais parfois, elles peuvent être subtiles et nous devons prendre des décisions judicieuses.
Supposons que nous ajoutions une autre responsabilité à la classe Dog nommée catchThief (). Maintenant, cela pourrait mener à une responsabilité supplémentaire supplémentaire. Demain, si le département de la police doit changer le comportement du chien qui attrape le voleur, il devra changer de classe de chien. Dans ce cas, il serait préférable de créer une autre sous-classe et nommez-la ThiefCathcerDog. Mais dans un autre point de vue, si nous sommes certains que cela ne changera en aucun cas, ou que la manière dont catchThief a été mis en œuvre dépend de certains paramètres externes, il est parfaitement correct d'avoir cette responsabilité. Si les responsabilités ne sont pas étonnamment étranges, nous devons le décider judicieusement en fonction du cas d'utilisation.
la source
"Une raison de changer" dépend de qui utilise le système. Assurez-vous de disposer des cas d'utilisation pour chaque acteur et dressez une liste des modifications les plus probables. Pour chaque changement de cas d'utilisation possible, assurez-vous qu'une seule classe serait affectée par ce changement. Si vous ajoutez un cas d'utilisation entièrement nouveau, assurez-vous que vous n'aurez besoin que d'étendre une classe pour le faire.
la source