Pour clarifier, ce que je demande, c'est
public class A{
private/*or public*/ B b;
}
vs.
public class A{
private/*or public*/ class B{
....
}
}
Je peux certainement penser à quelques raisons d'utiliser l'un 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.
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.
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 de manière abusive. 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.
À mon avis, c'est un bon flic d'utiliser des classes définies en interne pour les classes 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.
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 une telle égalité. Vous utilisez simplement le contrôle de la portée et de l'accessibilité fourni par la classe.
J'évite les classes internes dans Java parce que 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 ajoutent vers le haut.
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.
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 aux 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 qu'un objet de type callback lui soit transmis. Ce rappel a une signature de fonction 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 private do_callback 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 ()".
Selon Jon Skeet dans C # in Depth, l'utilisation d'une classe imbriquée était le seul moyen d'implémenter un parleton singular thread-safe paresseux (voir cinquième version) (jusqu'à .NET 4).
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 réalisé 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.