Cette question concerne l’opportunité de transformer une classe imbriquée en Java en classe imbriquée statique ou en classe imbriquée interne. J'ai cherché ici et sur Stack Overflow, mais je n'ai trouvé aucune question concernant les implications de cette décision sur la conception.
Les questions que j'ai trouvées portent sur la différence entre les classes imbriquées statique et interne, ce qui est clair pour moi. Cependant, je n'ai pas encore trouvé de raison convaincante d'utiliser une classe imbriquée statique en Java - à l'exception des classes anonymes, que je ne considère pas pour cette question.
Voici ma compréhension de l'effet de l'utilisation de classes imbriquées statiques:
- Moins de couplage: Nous obtenons généralement moins de couplage, car la classe ne peut pas accéder directement aux attributs de sa classe externe. Moins de couplage signifie généralement une meilleure qualité de code, des tests plus faciles, une refactorisation, etc.
- Single
Class
: le chargeur de classe n'a pas besoin de s'occuper d'une nouvelle classe à chaque fois, nous créons un objet de la classe externe. Nous obtenons simplement de nouveaux objets pour la même classe, encore et encore.
Pour une classe interne, je trouve généralement que les gens considèrent l'accès aux attributs de la classe externe comme un pro. Je me démarquerai à cet égard du point de vue de la conception, car cet accès direct signifie que nous avons un couplage élevé et que si nous voulons jamais extraire la classe imbriquée dans sa classe de premier niveau séparée, nous ne pourrons le faire qu'après avoir tourné. dans une classe imbriquée statique.
Ma question se résume donc à ceci: ai-je tort de supposer que l'accès aux attributs disponible pour les classes internes non statiques conduit à un couplage élevé, donc à une qualité de code inférieure, et j'en déduis que des classes imbriquées (non anonymes) devraient généralement être statique?
Ou en d'autres termes: existe-t-il une raison convaincante de préférer une classe interne imbriquée?
Réponses:
Dans l’article 22 de son livre "Effective Java Second Edition", Joshua Bloch explique quand utiliser quel type de classe imbriquée et pourquoi. Il y a quelques citations ci-dessous:
Une utilisation courante d'une classe de membre statique est une classe d'assistance publique, utile uniquement en conjonction avec sa classe externe. Par exemple, considérons une énumération décrivant les opérations prises en charge par une calculatrice. Operation enum doit être une classe de membre statique publique de la
Calculator
classe. Les clients deCalculator
pourraient alors faire référence à des opérations utilisant des noms tels queCalculator.Operation.PLUS
etCalculator.Operation.MINUS
.Une utilisation courante d'une classe membre non statique consiste à définir un adaptateur permettant à une instance de la classe externe d'être considérée comme une instance d'une classe non liée. Par exemple, les implémentations de l'
Map
interface de utilisent généralement des classes de membres non statiques à mettre en œuvre leurs vues sur la collecte , qui sont renvoyés parMap
« skeySet
,entrySet
et desvalues
méthodes. De même, les implémentations des interfaces de collection, telles queSet
etList
, utilisent généralement des classes membres non statiques pour implémenter leurs itérateurs:Si vous déclarez une classe de membre qui ne nécessite pas l'accès à une instance englobante, insérez toujours le
static
modificateur dans sa déclaration, ce qui en fera une classe de membre statique plutôt que non statique.la source
MySet
instances de classe externes ? Je pourrais envisager que quelque chose comme un type dépendant du chemin soit une différence, c'est-à-diremyOneSet.iterator.getClass() != myOtherSet.iterator.getClass()
, mais encore une fois, en Java, cela n'est en fait pas possible, car la classe serait la même pour chacun.Vous avez raison de supposer que l'accès aux attributs disponible pour les classes internes non statiques entraîne un couplage élevé, donc une qualité de code inférieure, et que les classes internes (non anonymes et non locales ) doivent généralement être statiques.
Les implications de la décision de rendre les classes internes non statiques sont décrites dans Java Puzzlers , Puzzle 90 (la police en gras ci-dessous est le mien):
Si vous êtes intéressé, une analyse plus détaillée de Puzzle 90 est fournie dans cette réponse à Stack Overflow .
Il est à noter que ce qui précède est essentiellement une version étendue des instructions données dans le tutoriel sur les classes et objets Java :
Ainsi, la réponse à la question que vous a demandé en d' autres termes par tutoriel est, la seule convaincre raison d'utiliser non-statique lorsque l' accès à des champs non publics d'une instance englobante et des méthodes est nécessaire .
La formulation du didacticiel est quelque peu large (c'est peut-être pourquoi Java Puzzlers tente de le renforcer et de le réduire). En particulier, accéder directement aux champs d'instance englobants n'a jamais été réellement nécessaire , dans la mesure où d'autres méthodes, telles que leur transmission en tant que paramètres constructeur / méthode, devenaient toujours plus faciles à déboguer et à gérer.
Globalement, mes rencontres (assez pénibles) avec le débogage de classes internes qui accédaient directement à des champs d'instance englobante donnaient la forte impression que cette pratique ressemblait à l'utilisation d'un état global, avec les maux connus qui lui étaient associés.
Bien sûr, Java fait en sorte que les dommages d’un tel "quasi global" soient contenus dans la classe englobante, mais lorsque je devais déboguer une classe interne particulière, c’était comme si un tel bandeau ne permettait pas de réduire la douleur: j’avais encore garder à l'esprit la sémantique et les détails «étrangers» au lieu de se concentrer entièrement sur l'analyse d'un objet problématique particulier.
Par souci d'exhaustivité, il peut y avoir des cas où le raisonnement ci-dessus ne s'applique pas. Par exemple, à ma lecture des javadocs map.keySet , cette fonctionnalité suggère un couplage étroit et, par conséquent, invalide les arguments par rapport aux classes non statiques:
Cela ne faciliterait pas la maintenance, le test et le débogage du code impliqué, mais cela pourrait au moins permettre de dire que la complication correspond / est justifiée par la fonctionnalité voulue.
la source
Quelques idées fausses:
IIRC - En fait, c'est possible. (Bien sûr, s’il s’agit de champs d’objet, il n’aura pas d’objet externe à examiner. Néanmoins, s’il récupère un tel objet de n’importe où, il pourra accéder directement à ces champs).
Je ne suis pas tout à fait sûr de ce que vous voulez dire ici. Le chargeur de classes n'est impliqué que deux fois au total, une fois pour chaque classe (lorsque la classe est chargée).
Randonnée suit:
En Javaland, il semble que nous ayons un peu peur de créer des classes. Je pense que cela doit se sentir comme un concept important. Il appartient généralement à son propre fichier. Il a besoin d'un passe-partout important. Il a besoin d'attention à sa conception.
Vers d'autres langages - par exemple Scala, ou peut-être C ++: Il n'y a absolument aucun drame créant un détenteur minuscule (classe, struct, peu importe) à chaque fois que deux bits de données appartiennent ensemble. Les règles d'accès flexibles vous aident en coupant le passe-partout, vous permettant de garder le code associé physiquement plus près. Cela réduit votre "distance mentale" entre les deux classes.
Nous pouvons éviter les problèmes de conception liés à l’accès direct en maintenant la classe privée privée.
(... Si vous avez demandé « pourquoi un non-statique publique classe intérieure ?», C'est une très bonne question - je ne pense pas avoir encore trouvé de cas justifié à leur place).
Vous pouvez les utiliser à tout moment, car cela aide à la lisibilité du code et permet d’éviter les violations de DRY et le standard. Je n'ai pas un exemple concret parce que cela ne se produit pas très souvent dans mon expérience, mais cela se produit.
Les classes internes statiques sont toujours une bonne approche par défaut , au fait. Non statique a un champ caché supplémentaire généré par lequel la classe interne fait référence à la classe extérieure.
la source
Il peut être utilisé pour créer un motif d'usine. Un modèle d'usine est souvent utilisé lorsque les objets créés nécessitent des paramètres de constructeur supplémentaires pour fonctionner, mais sont fastidieux à fournir à chaque fois:
Considérer:
Utilisé comme:
La même chose pourrait être réalisée avec une classe interne non statique:
Utilisé comme:
Les usines bénéficient de méthodes spécifiquement nommées, comme
create
,createFromSQL
oucreateFromTemplate
. La même chose peut être faite ici aussi sous la forme de plusieurs classes internes:Moins de passages de paramètres sont nécessaires de la classe d'usine à la classe construite, mais la lisibilité peut en souffrir un peu.
Comparer:
contre
la source