Quand devez-vous utiliser une classe privée / interne?

21

Pour clarifier, ce que je demande, c'est

public class A{
    private/*or public*/ B b;
}

contre.

public class A{
    private/*or public*/ class B{
        ....
    }
}

Je peux certainement penser à certaines raisons d'utiliser l'une ou l'autre, mais ce que j'aimerais vraiment voir, ce sont des exemples convaincants qui montrent que les avantages et les inconvénients ne sont pas seulement académiques.

EpsilonVector
la source
2
La réponse dépendrait des installations fournies par les bibliothèques de langues et de base. En supposant un langage C #, essayez de les exécuter par StyleCop. Je suis sûr que vous obtiendrez un avertissement sur la séparation de cette classe dans son propre fichier. Cela signifie que suffisamment de personnes chez MSFT pensent que vous n'avez pas besoin d'utiliser l'un des exemples que vous avez utilisés.
Job
Quel serait un exemple convaincant si les exemples académiques ne sont pas assez bons?
@Job StyleCop ne donnera un avertissement que si les classes internes sont visibles de l'extérieur. S'il est privé, il est tout à fait correct et a de nombreuses utilisations.
julealgon

Réponses:

20

Ils sont généralement utilisés lorsqu'une classe est un détail d'implémentation interne d'une autre classe plutôt qu'une partie de son interface externe. Je les ai surtout vus comme des classes de données uniquement qui rendent les structures de données internes plus propres dans les langages qui n'ont pas de syntaxe distincte pour les structures de style C. Ils sont également utiles parfois pour des objets dérivés d'une méthode hautement personnalisés comme les gestionnaires d'événements ou les threads de travail.

Karl Bielefeldt
la source
2
Voilà comment je les utilise. Si une classe n'est, d'un point de vue fonctionnel, rien d'autre qu'un détail d'implémentation privée d'une autre classe, alors elle devrait être déclarée de cette façon, tout comme une méthode ou un champ privé le serait. Aucune raison de polluer l'espace de noms avec des ordures qui ne seront jamais utilisées ailleurs.
Aaronaught
9

Supposons que vous construisez un arbre, une liste, un graphique, etc. Pourquoi devriez-vous exposer les détails internes d'un nœud ou d'une cellule au monde extérieur?

Toute personne utilisant un graphique ou une liste ne doit compter que sur son interface, et non sur son implémentation, car vous souhaiterez peut-être la changer un jour à l'avenir (par exemple, d'une implémentation basée sur un tableau à une implémentation basée sur un pointeur) et les clients utilisant votre la structure des données ( chacune d'elles ) devrait modifier son code afin de l'adapter à la nouvelle implémentation.

Au lieu de cela, l'encapsulation de l'implémentation d'un nœud ou d'une cellule dans une classe interne privée vous donne la liberté de modifier l'implémentation à tout moment, sans que les clients soient obligés d'ajuster leur code en conséquence, tant que l'interface de votre structure de données reste intacte.

Masquer les détails de l'implémentation de votre structure de données entraîne également des avantages de sécurité, car si vous souhaitez distribuer votre classe, vous ne rendrez disponible que le fichier d'interface avec le fichier d'implémentation compilé et personne ne saura si vous utilisez réellement des tableaux ou des pointeurs pour votre implémentation, protégeant ainsi votre application d'une sorte d'exploitation ou, au moins, de connaissances en raison de l'impossibilité d'utiliser ou d'inspecter votre code. En plus des problèmes pratiques, ne sous-estimez pas le fait que c'est une solution extrêmement élégante dans de tels cas.

Nicola Lamonaca
la source
5

À mon avis, c'est un flic juste d'utiliser des classes définies en interne pour les classes qui sont utilisées brièvement dans une classe pour permettre une tâche spécifique.

Par exemple, si vous devez lier une liste de données composée de deux classes indépendantes:

public class UiLayer
{
    public BindLists(List<A> as, List<B> bs)
    {
        var list = as.ZipWith(bs, (x, y) => new LayerListItem { AnA = x, AB = y});
        // do stuff with your new list.
    }

    private class LayerListItem
    {
        public A AnA;
        public B AB;
    }
}

Si votre classe interne est utilisée par une autre classe, vous devez la séparer. Si votre classe interne contient une logique, vous devez la séparer.

Fondamentalement, je pense qu'ils sont parfaits pour boucher les trous dans vos objets de données, mais ils sont difficiles à maintenir s'ils doivent contenir de la logique, car vous devrez savoir où les chercher si vous devez les changer.

Ed James
la source
2
En supposant que C #, ne pouvez-vous pas simplement utiliser des tuples ici?
Job
3
@Job Bien sûr, mais parfois, tuple.ItemXle code n'est pas clair.
Lasse Espeholt
2
@Job yup, lasseespeholt l'a bien compris. Je préfère voir une classe interne de {string Title; chaîne Description; } qu'un Tuple <chaîne, chaîne>.
Ed James
à ce stade, ne serait-il pas logique de mettre cette classe dans son propre fichier?
Job
Non, pas si vous ne l'utilisez vraiment que pour lier brièvement certaines données afin de réaliser une tâche qui ne sort pas du domaine de compétence de la classe parent. Du moins, pas à mon avis!
Ed James
4
  • Les classes internes dans certains langages (Java par exemple) ont un lien avec un objet de leur classe contenante et peuvent utiliser leurs membres sans les qualifier. Lorsqu'elles sont disponibles, il est plus clair que de les réimplémenter à l'aide d'autres ressources linguistiques.

  • Les classes imbriquées dans d'autres langages (C ++ par exemple) n'ont pas ce lien. Vous utilisez simplement le contrôle de la portée et de l'accessibilité fourni par la classe.

AProgrammer
la source
3
En fait, Java a les deux .
Joachim Sauer
3

J'évite les classes internes en Java car le remplacement à chaud ne fonctionne pas en présence de classes internes. Je déteste cela parce que je déteste vraiment compromettre le code pour de telles considérations, mais ces redémarrages de Tomcat s'additionnent.

Kevin Cline
la source
ce problème est-il documenté quelque part?
moucher
Downvoter: mon affirmation est-elle incorrecte?
kevin cline
1
J'ai voté contre parce que votre réponse manque de détails, pas parce qu'elle est incorrecte. Le manque de détails rend votre affirmation difficile à vérifier. Si vous ajoutez plus sur cette question, je reviendrais probablement sur upvote, car vos informations semblent assez intéressantes et peuvent être utiles
gnat
1
@gnat: J'ai fait quelques recherches et bien que cela semble être une vérité bien connue, je n'ai pas trouvé de référence définitive sur les limites de Java HotSwap.
Kevin Cline,
nous ne sommes pas sur Skeptics.SE :) juste une référence ferait l'affaire, cela n'a pas besoin d'être définitif
gnat
2

L'une des utilisations de la classe interne statique privée est le modèle de mémo. Vous mettez toutes les données qui doivent être mémorisées dans une classe privée et les renvoyez d'une fonction en tant qu'objet. Personne à l'extérieur ne peut (sans réflexion, désérialisation, inspection de la mémoire ...) l'examiner / le modifier, mais il peut le rendre à votre classe et il peut restaurer son état.

user470365
la source
2

Une raison d'utiliser une classe interne privée peut être due au fait qu'une API que vous utilisez nécessite l'héritage d'une classe particulière, mais vous ne voulez pas exporter la connaissance de cette classe vers les utilisateurs de votre classe externe. Par exemple:

// async_op.h -- someone else's api.
struct callback
{
    virtual ~callback(){}
    virtual void fire() = 0;
};

void do_my_async_op(callback *p);



// caller.cpp -- my api
class caller
{
private :
    struct caller_inner : callback
    {
        caller_inner(caller &self) : self_(self) {}
        void fire() { self_.do_callback(); }
        caller &self_;
    };

    void do_callback()
    {
        // Real callback
    }

    caller_inner inner_;

public :
    caller() : inner_(*this) {}

    void do_op()
    {
        do_my_async_op(&inner_);
    }
};

Dans ce cas, do_my_async_op nécessite de lui passer un objet de type callback. Ce rappel a une signature de fonction de membre public que l'API utilise.

Lorsque do_op () est appelé à partir de la classe externe, il utilise une instance de la classe interne privée qui hérite de la classe de rappel requise au lieu d'un pointeur sur lui-même. La classe externe a une référence à la classe externe dans le simple but de shunter le rappel dans la fonction membre do_callback privée de la classe externe .

L'avantage de ceci est que vous êtes sûr que personne d'autre ne peut appeler la fonction membre publique "fire ()".

Kaz Dragon
la source
2

Selon Jon Skeet dans C # in Depth, l'utilisation d'une classe imbriquée était le seul moyen d'implémenter un singleton thread-safe entièrement paresseux (voir cinquième version) (jusqu'à .NET 4).

Scott Whitlock
la source
1
c'est l' idiome du titulaire d'initialisation à la demande , en Java, il nécessite également une classe interne statique privée. Il est recommandé dans la FAQ JMM , par Brian Goetz dans JCiP 16.4 et par Joshua Bloch dans JavaOne 2008 Java plus efficace (pdf) "Pour de hautes performances sur un champ statique ..."
gnat
1

Les classes privées et internes sont utilisées pour augmenter les niveaux d'encapsulation et masquer les détails d'implémentation.

En C ++, en plus d'une classe privée, le même concept peut être obtenu en implémentant l'espace de noms anonyme d'une classe cpp. Cela sert bien à cacher / privatiser un détail d'implémentation.

C'est la même idée qu'une classe interne ou privée mais à un niveau d'encapsulation encore plus élevé car elle est complètement invisible en dehors de l'unité de compilation du fichier. Rien de tout cela n'apparaît dans le fichier d'en-tête ou n'est visible de l'extérieur dans la déclaration de la classe.

hiwaylon
la source
Des commentaires constructifs sur le -1?
hiwaylon
1
Je n'ai pas downvote mais je suppose que vous ne répondez pas vraiment à la question: vous ne donnez aucune raison d'utiliser la classe privée / interne. Vous venez de décrire comment cela fonctionne et comment faire une chose similaire en c ++
Simon Bergot
En effet. Je pensais que c'était évident pour moi et les autres réponses (masquer les détails d'implémentation, augmentation des niveaux d'encapsulation, etc.) mais je vais essayer de clarifier certains. Merci @Simon.
hiwaylon
1
Joli montage. Et s'il vous plaît, ne vous offusquez pas à cause de ce genre de réaction. Le réseau SO essaie d'appliquer des politiques pour améliorer le rapport signal / bruit. Certaines personnes sont assez strictes sur la portée des questions / réponses et oublient parfois d'être gentilles (en commentant leur downvote par exemple)
Simon Bergot
@Simon Merci. Il est décevant en raison de la mauvaise qualité de communication qu'il présente. Peut-être est-il un peu trop facile d'être "strict" derrière le voile anonyme du web? Oh bien rien de nouveau je suppose. Merci encore pour la rétroaction positive.
hiwaylon