L'héritage multiple viole-t-il le principe de responsabilité unique?

18

Si vous avez une classe qui hérite de deux classes distinctes, cela ne signifie-t-il pas que votre sous-classe fait automatiquement (au moins) 2 choses, une de chaque superclasse?

Je crois qu'il n'y a aucune différence si vous avez un héritage d'interfaces multiples.

Edit: Pour être clair, je crois que si le sous-classement de plusieurs classes viole SRP, alors la mise en œuvre de plusieurs interfaces (non marqueurs ou interface de base (par exemple comparable)) viole également SRP.

m3th0dman
la source
Juste pour être clair, vous pensez également que la mise en œuvre de plusieurs interfaces viole le SRP?
Je crois que si la sous-classe de plusieurs classes viole SRP, la mise en œuvre de plusieurs interfaces (non marqueurs) viole également SRP.
m3th0dman
Je voterais en faveur de cette question, mais vous avez intégré votre opinion dans la question, avec laquelle je ne suis pas d'accord.
Nicole
1
@NickC: Je n'ai pas dit mon avis (vous auriez dû lire le commentaire ci-dessus); Je vais modifier la question pour la rendre plus claire.
m3th0dman du
1
@NickC Je n'ai pas d'opinion, c'est pourquoi j'ai demandé. J'ai seulement dit que les deux sont liés (héritage d'interface et héritage de classe); s'il viole pour l'héritage de classe alors il viole aussi pour l'héritage d'interface, s'il ne le fait pas pour l'un alors il ne viole pas non plus pour l'autre. Et je me fiche du vote
positif

Réponses:

17

Dans un sens très étroit, la réponse est "Oui": en supposant que vos classes ou interfaces de base sont conçues dans un seul but, l'héritage des deux crée une classe avec plusieurs responsabilités. Cependant, que ce soit ou non "une mauvaise chose" dépend de la nature des classes ou des interfaces dont vous héritez.

Vous pouvez partitionner vos classes et vos interfaces en deux groupes principaux - ceux qui traitent de la complexité essentielle de votre système et ceux qui traitent de sa complexité accidentelle. S'il hérite de plusieurs classes de "complexité essentielle", c'est mauvais; si vous héritez d'une classe "essentielle" et d'une ou plusieurs classes "accidentelles", c'est OK.

Par exemple, dans un système de facturation, vous pouvez avoir des classes pour représenter les factures et les cycles de facturation (elles traitent de la complexité essentielle) et des classes pour les objets persistants (elles traitent de la complexité accidentelle). Si vous héritez comme ça

class BillingCycleInvoice : public BillingCycle, public Invoice {
};

c'est mauvais: votre BillingCycleInvoiceresponsabilité est mixte en ce qui concerne la complexité essentielle du système.

D'un autre côté, si vous héritez comme ça

class PersistentInvoice : public Invoice, public PersistentObject {
};

votre classe est OK: techniquement, elle dessert deux préoccupations à la fois, mais comme une seule d'entre elles est essentielle, vous pouvez radier l'héritage accidentel du "coût de faire des affaires".

dasblinkenlight
la source
3
Votre deuxième exemple est très similaire à ce qui serait une préoccupation transversale dans la programmation orientée aspect. Cela signifie que vos objets doivent parfois faire des choses (comme la journalisation, les tests de contrôle d'accès ou la persistance) qui ne sont pas leur objectif principal, et que vous pouvez le faire avec un héritage multiple. C'est une utilisation appropriée de l'outil.
Le plus simple est Si la mise en œuvre d'une nouvelle interface nécessite de compléter la fonctionnalité de classe, ce n'est pas le cas. Mais si l'ajout d'une nouvelle responsabilité implémente l'interface avec une autre classe, puis l'injecter comme dépendance ne violera jamais SRP
Ramankingdom
9

Le SRP est une directive pour éviter les classes divines, qui sont mauvaises, mais il peut également être pris trop à la lettre et un projet démarre avec des tonnes de classes qui ne peuvent vraiment rien faire sans qu'une poignée soit combinée. L'héritage multiple / les interfaces multiples peuvent être un signe qu'une classe devient trop grande, mais cela pourrait aussi être un signe que votre concentration est beaucoup trop granulaire pour la tâche à accomplir et que votre solution est sur-conçue, ou elle pourrait simplement être parfaitement cas d'utilisation valide pour l'héritage multiple et vos inquiétudes ne sont pas fondées.

La conception de logiciels est autant de l'art que de la science, c'est un équilibre entre de nombreux intérêts concurrents. Nous avons des règles pour éviter que les choses que nous savons vont trop loin dans une direction qui causent des problèmes, mais ces règles peuvent tout aussi bien nous emmener dans un endroit tout aussi mauvais ou pire que ce que nous essayions d'éviter en premier lieu.

Ryathal
la source
4

Quelque peu. Concentrons-nous sur le principe principal de SRP:

Une classe ne devrait avoir qu'une seule raison de changer.

L'héritage multiple ne force pas cela, mais tend à y conduire. Si la classe remplace ou implémente ses classes de base, cela a tendance à être plus de raisons de changer. De cette façon, l'héritage multiple d'interface a tendance à être plus gênant que l'héritage de classe. Si deux classes sont héritées mais non remplacées, les raisons du changement existent dans les classes de base et non dans la nouvelle classe.

Les endroits où l'héritage multiple ne viole pas SRP est lorsque tous les types de base sauf un ne sont pas quelque chose que la classe implémente, mais agissent comme un trait que la classe a. Considérez IDisposableen C #. Les choses jetables n'ont pas deux responsabilités, l'interface agit simplement comme une vue dans les classes qui partagent le trait mais ont des responsabilités décidément différentes.

Telastyn
la source
2

Pas à mon avis. Pour moi, une seule responsabilité est de "modéliser un utilisateur de mon application". Il se peut que je doive fournir ces données via un service Web et les stocker dans une base de données relationnelle. Je pourrais fournir cette fonctionnalité en héritant de deux classes mixtes.

Cela ne signifie pas que la classe a trois responsabilités. Cela signifie qu'une partie de la responsabilité de modéliser un utilisateur nécessite la prise en charge de la sérialisation et de la persistance.

Kevin Cline
la source
2

Pas du tout. En Java, vous pouvez avoir une interface qui représente une sorte d'objet de domaine - un Foo, par exemple. Il peut être nécessaire de le comparer à d'autres objets Foo, et il implémente donc Comparable (avec la logique de comparaison externalisée dans une classe Comparator); il se peut que cet objet doive également être sérialisable, afin qu'il puisse être transporté à travers le réseau; et il peut être nécessaire de le cloner. Ainsi, la classe implémente trois interfaces mais n'a aucune logique interne pour les prendre en charge au-delà de celle prise en charge par Foo.

Cela dit, il est stupide de pousser le SRP à l'extrême. Si une classe définit plus d'une méthode, viole-t-elle le SRP? Tous les objets Java ont une méthode toString () et une méthode equals (Object), ce qui signifie que CHAQUE objet en Java est responsable de l'égalité et de l'auto-représentation. Est-ce une mauvaise chose?

Matthew Flynn
la source
1

Je suppose que cela dépend de la façon dont vous définissez la responsabilité. Dans l'exemple iostream classique, il ne viole pas le SRP. L'IOstream est responsable de la gestion d'un flux bidirectionnel.

Après avoir épuisé vos responsabilités primitives, vous passez à des responsabilités plus générales de haut niveau, après tout, comment définissez-vous la responsabilité de votre principal point d'entrée? Sa responsabilité doit être «être le principal point d'entrée» et non «tout ce que fait mon application», sinon il est impossible de ne pas violer le SRP et de produire une application.

stonemetal
la source