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?
object-oriented
anti-patterns
solid
Jonas Elfström
la source
la source
Réponses:
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.
la source
somehow
Je propose que cet anti-modèle soit nommé Jack of All Trades, ou peut-être trop de chapeaux.
la source
Si je devais lui donner un nom, je pense que je l'appellerais l' Hydre :
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:
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.
la source
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.
la source
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é.
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.
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.
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!
(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.
la source
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
extends
mot - 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.la source
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.
la source
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.
la source
"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".
la source
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 .
la source