Combien y a-t-il trop d'interfaces sur une classe? [fermé]

28

Je considérerais probablement comme une odeur de code ou même un anti-modèle d'avoir une classe qui implémente 23 interfaces. S'il s'agit bien d'un anti-modèle, comment l'appelleriez-vous? Ou est-ce tout simplement qu'il ne respecte pas le principe de responsabilité unique?

Jonas Elfström
la source
3
Eh bien, c'est discutable de toute façon. Si la classe a 23 méthodes qui servent toutes une seule responsabilité (toutes les classes n'ont pas besoin d'autant de méthodes, mais ce n'est pas impossible - surtout si la plupart sont des wrappers de commodité à trois lignes autour d'autres méthodes), vous avez juste des interfaces trop fines; )
La classe en question est une sorte d'entité et les interfaces sont pour la plupart des propriétés. Il y en a 113 et seulement quatre méthodes.
Jonas Elfström
6
Je pensais que les interfaces sont essentiellement utilisées pour le typage canard dans les langages compilés statiquement. Quel est le problème? :)
ashes999
2
juste une question: les 113 propriétés sont-elles toutes remplies pour chaque instance de cette entité? Ou s'agit-il d'une sorte «d'entité commune» utilisée dans différents contextes avec seulement quelques propriétés remplies?
David
1
Revenons à la compréhension conceptuelle de ce qui constitue une «classe». Une classe est une chose unique avec une responsabilité et un rôle clairement définis. Pouvez-vous définir votre objet 23 interfaces de cette manière? Pouvez-vous publier une seule phrase (non joycéenne) qui résume adéquatement votre classe? Si c'est le cas, 23 interfaces conviennent. Sinon, c'est trop grand, trop compliqué.
Stephen Gross

Réponses:

36

Famille royale: Ils ne font rien de particulièrement fou, mais ils ont un milliard de titres et sont en quelque sorte liés à la plupart des autres membres de la famille royale.

Ingénieur du monde
la source
:-)))))))))))))
Emilio Garavaglia
Je souligneraissomehow
Wolf
23

Je propose que cet anti-modèle soit nommé Jack of All Trades, ou peut-être trop de chapeaux.

Mike Partridge
la source
2
Et le couteau suisse?
FrustratedWithFormsDesigner
Il semble que ce terme soit déjà utilisé pour les interfaces avec trop de méthodes :-) Mais cela aurait également du sens ici.
Jalayn
1
@FrustratedWithFormsDesigner Un couteau suisse n'aurait jamais le mauvais goût d'avoir une interface nommée IValue qui contient 30 propriétés dont 20 sont précédées du nouveau mot-clé modificateur.
Jonas Elfström
3
+1 pour Trop de chapeaux ... pour bien cacher la divinité à plusieurs têtes
Newtopian
1
Un couteau suisse est un modèle de réutilisation. c'est-à-dire qu'une seule "lame" peut exécuter plusieurs méthodes, donc c'est probablement une mauvaise analogie!
James Anderson
17

Si je devais lui donner un nom, je pense que je l'appellerais l' Hydre :

Hydre

Dans la mythologie grecque, l'hydre lernéenne (grec: Λερναία Ὕδρα) était une ancienne bête d'eau chthonique sans nom ressemblant à un serpent, avec des traits reptiliens, (comme son nom le prouve) qui possédait de nombreuses têtes - les poètes mentionnent plus de têtes que les peintres de vase peinture, et pour chaque tête coupée, il en a augmenté de deux de plus - et un souffle toxique si virulent même ses traces étaient mortelles.

Il est particulièrement important de noter qu'il a non seulement de nombreuses têtes, mais qu'il en grandit de plus en plus et qu'il est impossible de les tuer à cause de cela. C'est mon expérience avec ce genre de conceptions; les développeurs continuent à y brancher de plus en plus d'interfaces jusqu'à ce qu'il en ait trop pour tenir sur l'écran, et à ce moment-là, il est devenu si profondément ancré dans la conception et les hypothèses du programme que le fractionner est une perspective désespérée (si vous essayez, vous en réalité finissent souvent par avoir besoin de plus d' interfaces pour combler le fossé).

Un des premiers signes d'une catastrophe imminente due à une "Hydre" est la conversion fréquente d'une interface à une autre, sans beaucoup de vérification de la raison, et souvent à une cible qui n'a aucun sens, comme dans:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

Il est évident, pris hors contexte, qu'il y a quelque chose de louche dans ce code, mais la probabilité que cela se produise augmente avec le nombre de "têtes" d'un objet, car les développeurs savent intuitivement qu'ils ont toujours affaire à la même créature.

Bonne chance pour avoir fait de la maintenance sur un objet qui implémente 23 interfaces. Les chances que vous ne causiez pas de dommages collatéraux au cours du processus sont minimes, voire nulles.

Aaronaught
la source
16

L' objet divin me vient à l'esprit; un seul objet qui sait TOUT faire. Cela vient du faible respect des exigences de "cohésion" des deux principales méthodologies de conception; si vous avez un objet avec 23 interfaces, vous avez un objet qui sait être 23 choses différentes pour ses utilisateurs, et ces 23 choses différentes ne sont probablement pas dans le sens d'une seule tâche ou zone du système.

Dans SOLID, alors que le créateur de cet objet a manifestement essayé de suivre le principe de ségrégation d'interface, ils ont violé une règle antérieure; Principe de responsabilité unique. Il y a une raison pour laquelle c'est "SOLIDE"; S vient toujours en premier lorsque l'on pense à la conception, et toutes les autres règles suivent.

Dans GRASP, le créateur d'une telle classe a négligé la règle "Haute Cohésion"; GRASP, contrairement à SOLID, enseigne qu'un objet n'a pas à avoir une seule responsabilité, mais au plus il devrait avoir deux ou trois responsabilités très liées.

KeithS
la source
Je ne suis pas sûr qu'il s'agisse de l'anti-modèle God Object, car Dieu n'est contraint par aucune interface. Être contraint par tant d'interfaces pourrait réduire la toute-puissance de Dieu. ;) Peut-être que c'est un objet chimère?
FrustratedWithFormsDesigner
Dieu a de nombreux visages et peut représenter beaucoup de choses pour beaucoup de gens. Si la fonctionnalité des interfaces combinées rend l'objet "omniscient", il n'est pas vraiment contraignant pour Dieu, n'est-ce pas?
KeithS
1
A moins que celui qui a créé les interfaces ne les modifie. Ensuite, Dieu devra peut-être être réimplémenté pour se conformer aux changements d'interface.
FrustratedWithFormsDesigner
3
Ça fait mal que vous pensiez que c'est moi qui ai créé la classe.
Jonas Elfström
L'utilisation de "vous" est plus le pluriel vous, se référant à votre équipe de développement. QUELQU'UN dans cette équipe, passée ou présente, a créé cette classe. Je vais modifier, cependant.
KeithS
8

23 n'est qu'un nombre! Dans le scénario le plus probable, il est suffisamment élevé pour alarmer. Cependant, si nous demandons, quel est le nombre le plus élevé de méthodes / interfaces avant de pouvoir appeler une balise comme un "anti-modèle"? Est-ce 5 ou 10 ou 25? Vous vous rendez compte que ce nombre n'est vraiment pas une réponse parce que si 10 est bon, 11 peut également être - et ensuite tout entier après cela.

La vraie question concerne la complexité. Et nous devons souligner que le long code, le nombre de méthodes ou la grande taille de la classe par aucune mesure n'est en fait PAS la définition de la complexité. Oui, un code de plus en plus gros (plus grand nombre de méthodes) rend difficile la lecture et la compréhension pour un nouveau débutant. Il gère également des fonctionnalités potentiellement variées, un grand nombre d'exceptions et des algorithmes assez évolués pour divers scénarios. Cela ne signifie pas qu'il est complexe - il est seulement difficile à digérer.

D'un autre côté, un code de taille relativement petite que l'on peut espérer lire en quelques heures à l'intérieur et à l'extérieur peut encore être complexe. Voici quand je pense que le code est (inutilement) complexe.

Chaque morceau de sagesse de la conception orientée objet peut être mis ici pour définir "complexe" mais je limiterais ici pour montrer quand "tant de méthodes" est une indication de complexité.

  1. Connaissance mutuelle. (aka couplage) Souvent, lorsque les choses sont écrites sous forme de classes, nous pensons tous qu'il s'agit d'un code orienté objet "sympa". Mais l'hypothèse concernant l'autre classe rompt essentiellement l'encapsulation réellement nécessaire. Lorsque vous avez des méthodes qui "fuient" des détails profonds sur l'état interne des algorithmes - et que l'application est construite avec des hypothèses clés sur l'état interne de la classe de desserte.

  2. Trop de répétitions (effraction) Lorsque des méthodes qui ont des noms similaires mais qui sont contradictoires fonctionnent - ou des noms contradictoires avec des fonctionnalités similaires. De nombreux codes évoluent pour prendre en charge des interfaces légèrement différentes pour différentes applications.

  3. Trop de rôles Lorsque la classe continue d'ajouter des fonctions secondaires et continue de se développer davantage comme les gens l'aiment, seulement pour savoir que la classe est en effet maintenant une classe à deux. Étonnamment, tout cela commence par une véritableexigences et aucune autre classe n'existe pour ce faire. Considérez ceci, il existe une classe Transaction, qui indique les détails de la transaction. Ça a l'air bien jusqu'à présent, maintenant quelqu'un nécessite une conversion de format au "moment de la transaction" (entre UTC et similaire), plus tard, les gens ajoutent des règles pour vérifier si certaines choses étaient à certaines dates pour valider les transactions invalides. - Je n'écrirai pas toute l'histoire mais finalement, la classe de transaction construit le calendrier entier avec et ensuite les gens commencent à en utiliser "seulement le calendrier"! C'est très complexe (à imaginer) pourquoi vais-je instancier une "classe de transaction" pour avoir des fonctionnalités qu'un "calendrier" me fournirait!

  4. (In) cohérence de l'API Quand je fais book_a_ticket () - Je réserve un ticket! C'est très simple, quel que soit le nombre de contrôles et de processus nécessaires pour y arriver. Maintenant, cela devient complexe lorsque le flux de temps commence à effectuer cela. En règle générale, on autorise la «recherche» et le statut disponible / indisponible possible, puis pour réduire les retours en arrière, vous commencez à enregistrer certains pointeurs de contexte à l'intérieur du ticket et vous vous référez ensuite à réserver le ticket. La recherche n'est pas la seule fonctionnalité - les choses empirent après de nombreuses «fonctionnalités secondaires». Dans le processus, la signification de book_a_ticket () implique book_that_ticket ()! et cela peut être incroyablement complexe.

Il y a probablement beaucoup de telles situations que vous verriez dans un code très évolué et je suis sûr que beaucoup de gens peuvent ajouter des scénarios, où "tant de méthodes" n'ont tout simplement pas de sens ou ne font pas ce que vous pensez évidemment. Ceci est l'anti-modèle.

Mon expérience personnelle est que lorsque des projets qui démarrent raisonnablement vers le bas, de nombreuses choses pour lesquelles il aurait dû y avoir de véritables classes par elles-mêmes - les restes enfouis ou pire restent toujours divisés entre différentes classes et le couplage augmente. Le plus souvent, ce qui aurait mérité 10 classes, mais n'en a que 4, il est probable que beaucoup d'entre elles ont une confusion multi-usages et un grand nombre de méthodes. Appelez cela un DIEU et un DRAGON, c'est ce qui est MAUVAIS.

MAIS vous rencontrez des classes TRÈS GRANDES qui sont parfaitement cohérentes, elles ont 30 méthodes - et sont toujours très propres. Ils peuvent être bons.

Dipan Mehta
la source
La classe en questions a 23 interfaces et celles-ci contiennent, au total, 113 propriétés publiques et quatre méthodes. Seuls certains d'entre eux sont en lecture seule. C # a des propriétés implémentées automatiquement, c'est pourquoi je diffère entre les méthodes et les propriétés msdn.microsoft.com/en-us/library/bb384054.aspx
Jonas Elfström
7

Les classes doivent avoir exactement le bon nombre d'interfaces; Ni plus ni moins.

Dire «trop» serait impossible sans regarder si toutes ces interfaces servent ou non un objectif utile dans cette classe. Déclarer qu'un objet implémente une interface signifie que vous devez implémenter ses méthodes si vous vous attendez à ce que la classe compile. (Je suppose que la classe que vous regardez fait.) Si tout dans l'interface est implémenté et que ces implémentations font quelque chose par rapport aux entrailles de la classe, il serait difficile de dire que l'implémentation ne devrait pas être là. Le seul cas où je peux penser où une interface répondant à ces critères n'appartiendrait pas est externe: quand personne ne l'utilise.

Certains langages permettent de "sous-classer" les interfaces à l'aide d'un mécanisme comme le extendsmot - clé Java , et celui qui l'a écrit peut ne pas le savoir. Il se peut aussi que les 23 soient suffisamment éloignés pour que leur agrégation n’ait pas de sens.

Blrfl
la source
J'ai pensé que cette question était assez indépendante de la langue. Le code réel est en C #.
Jonas Elfström
Je n'ai fait aucun C #, mais il semble prendre en charge une forme similaire d'héritage d'interface.
Blrfl
Oui, une interface peut "hériter" d'autres interfaces et ainsi de suite.
Jonas Elfström
Je trouve intéressant que de nombreuses discussions se concentrent sur ce que les classes doivent faire, plutôt que sur les instances . Un robot avec une variété de capteurs d'imagerie et audio attachés à un châssis commun doit être modélisé comme une entité qui a un emplacement et une orientation, et qui peut voir, entendre et associer des images et des sons. Toute la vraie logique derrière ces fonctions peut être dans d'autres classes, mais le robot lui-même devrait prendre en charge des méthodes telles que "voir si des objets sont devant", "écouter les bruits dans la zone" ou "voir si l'objet devant ressemble à générer les sons détectés. "
supercat
Il est possible qu'un robot particulier subdivise sa fonctionnalité en sous-systèmes d'une manière particulière, mais dans la mesure où le code pratique qui utilise le robot ne devrait pas avoir à connaître ou à se soucier de la façon dont la fonctionnalité d'un robot particulier est subdivisée. Si les "yeux" et les "oreilles" étaient exposés au monde extérieur comme des objets séparés, mais que les capteurs correspondants étaient sur un bras d'extension partagé, le code extérieur pourrait demander aux "oreilles" d'écouter les sons au niveau du sol en même temps que il a demandé aux "yeux" de regarder au-dessus d'un obstacle.
supercat
1

Ses sons semblent avoir une paire "getter" / "setter" pour chaque propriété, comme c'est normal pour un "bean" typique et ont promu toutes ces méthodes à l'interface. Alors que diriez-vous d'appeler un " haricot ". Ou la "flatulence" plus rabelaisienne après les effets bien connus de trop de haricots.

James Anderson
la source
C'est en C # et il a des propriétés implémentées automatiquement. Ces 23 interfaces, au total, contiennent 113 propriétés de ce type.
Jonas Elfström
1

Le nombre d'interfaces augmente souvent lorsqu'un objet générique est utilisé dans différents environnements. En C #, les variantes de IComparable, IEqualityComparer et IComparer permettent de trier dans des configurations distinctes, donc vous pourriez finir par les implémenter toutes, certaines d'entre elles peut-être plus d'une fois puisque vous pouvez implémenter les versions génériques fortement typées ainsi que les versions non génériques , vous pouvez en outre implémenter plusieurs génériques.

Prenons un exemple de scénario, disons une boutique en ligne où vous pouvez acheter des crédits avec lesquels vous pouvez acheter autre chose (les sites de photos utilisent souvent ce schéma). Vous pourriez avoir une classe "Valuta" et une classe "Credits" qui héritent de la même base. Valuta a quelques surcharges d'opérateur astucieuses et des routines de comparaison qui vous permettent de faire des calculs sans vous soucier de la propriété "Currency" (ajouter des livres en dollars par exemple). Les crédits sont beaucoup plus simples mais ont un autre comportement distinct. En voulant pouvoir les comparer les uns aux autres, vous pourriez finir par implémenter IComparable ainsi que IComparable et les autres variantes d'interfaces de comparaison sur les deux (même s'ils utilisent une implémentation commune que ce soit dans la classe de base ou ailleurs).

Lors de l'implémentation de la sérialisation, ISerializable, IDeserializationCallback sont implémentés. Ensuite, implémenter des piles annuler-refaire: IClonable est ajouté. Fonctionnalité IsDirty: IObservable, INotifyPropertyChanged. Permettre aux utilisateurs de modifier les valeurs à l'aide de chaînes: IConvertable ... La liste peut continuer indéfiniment ...

Dans les langues modernes, nous voyons une tendance différente qui aide à séparer ces aspects et à les placer dans leurs propres classes, en dehors de la classe principale. La classe ou l'aspect extérieur est ensuite associé à la classe cible à l'aide d'annotations (attributs). Il est souvent possible de rendre les classes d'aspect externes plus ou moins génériques.

L'utilisation d'attributs (annotation) est sujette à réflexion. Un inconvénient est une perte de performance mineure (initiale). Un inconvénient (souvent émotionnel) est que les principes tels que l'encapsulation doivent être assouplis.

Il existe toujours d'autres solutions, mais pour chaque solution astucieuse, il y a un compromis ou un problème. Par exemple, l'utilisation d'une solution ORM peut exiger que toutes les propriétés soient déclarées virtuelles. Les solutions de sérialisation peuvent exiger des constructeurs par défaut sur vos classes. Si vous utilisez l'injection de dépendances, vous pourriez finir par implémenter 23 interfaces sur une seule classe.

À mes yeux, 23 interfaces ne doivent pas nécessairement être mauvaises par définition. Il pourrait y avoir un schéma bien pensé derrière cela, ou une détermination de principe telle que d'éviter l'utilisation de la réflexion ou des croyances d'encapsulation extrêmes.

Chaque fois que vous changez de travail ou que vous devez vous baser sur une architecture existante. Mon conseil est de se familiariser d'abord, ne pas essayer de tout refaçonner trop rapidement. Écoutez le développeur d'origine (s'il est toujours là) et essayez de comprendre les pensées et les idées derrière ce que vous voyez. Lorsque vous posez des questions, ne le faites pas pour le décomposer, mais pour apprendre ... Oui, tout le monde a ses propres marteaux en or, mais plus vous pouvez collecter de marteaux, plus vous vous entendrez facilement avec vos collègues.

Louis Somers
la source
0

"Trop" est subjectif: style de programmation? performance? conformité aux normes? précédents? un simple sentiment de confort / confiance?

Tant que votre code se comporte correctement et ne présente aucun problème de maintenabilité, 23 pourrait même être la nouvelle norme. Un jour, je pourrais alors dire: "Vous pouvez faire un excellent travail avec 23 interfaces, voir: Jonas Elfström".

Kris
la source
0

Je dirais que la limite devrait être un nombre inférieur à 23 - plus comme 5 ou 7,
cependant, cela n'inclut pas le nombre d'interfaces dont ces interfaces héritent ou le nombre d'interfaces implémentées par les classes de base.

(Donc, au total, N + n'importe quel nombre d'interfaces héritées, où N <= 7.)

Si votre classe implémente trop d'interfaces, c'est probablement une classe divine .

Danny Varod
la source