Je travaille sur un code hérité et j'ai rencontré quelque chose dont je ne suis pas sûr. Nous avons un class y
qui est déclaré à l'intérieur d'un autre class x
. Class y
n'est utilisé que dans class x
mais ma question est pourquoi ne pas créer un fichier de classe séparé et mettre class y
là-dedans au lieu de le déclarer à l'intérieur de class x
? N'est-ce pas une violation des POO ou est-ce juste une question de style car il n'est utilisé que dans cette classe. Je refactorise une partie de ce code et ma première réaction serait de séparer class y
dans son propre fichier.
namespace Library
{
public class x
{
// methods, properties, local members of class x
class y
{
// methods, properties, local members of class y
}
}
}
Vous créez une classe interne car elle n'est utilisée que dans le cadre de la classe x et elle s'inscrit logiquement dans l'affacturage/l'architecture de la classe x.
La classe y peut également être informée des détails d'implémentation de la classe x qui ne sont pas destinés à être connus du public.
Cela a des implications sur les autorisations. Une "classe y" de niveau supérieur serait "interne" - cependant, ici "y" est privé de "x". Cette approche est utile pour les détails d'implémentation (par exemple, les lignes de cache, etc.). De même, y
a accès à tous les états privés de x
.
Il y a aussi des implications avec les génériques; x<T>.y
est générique "de T", hérité de la classe externe. Vous pouvez le voir ici, où Bar
utilise pleinement T
- et notez que tous les champs statiques de Bar
sont limités par -T
.
class Foo<T> {
void Test(T value) {
Bar bar = new Bar();
bar.Value = value;
}
class Bar {
public T Value { get; set; }
}
}
Souvent, les gens pensent à tort qu'ils doivent définir Bar
comme Bar<T>
- c'est maintenant (effectivement) doublement générique - c'est-à-dire Foo<TOld, T>
- où TOld
est le (désormais indisponible) T
de Foo<T>
. Alors ne fais pas ça! Ou si vous voulez qu'il soit doublement générique, choisissez des noms différents. Heureusement, le compilateur vous en avertit ...
Ce code est très bien pour la raison exacte que vous avez donnée - "la classe y n'est utilisée que dans la classe x". Ce sont types imbriqués et l'une des directives pour les utiliser est que les types imbriqués doivent être étroitement couplés à leur type déclarant et ne doivent pas être utiles comme type à usage général. De cette façon, la classe imbriquée est inaccessible aux autres classes, mais vous permet toujours de suivre les principes orientés objet.
Je pense que c'est ok, tant que la classe contenue n'est utilisée que comme utilitaire. J'utilise ce type de construction par exemple pour définir des types de retour complexes pour des méthodes privées.
Je viens de parcourir le code que je mets à jour (et j'ai écrit à l'origine) et j'ai supprimé toutes les classes imbriquées. Malheureusement, j'ai à l'origine utilisé la classe imbriquée en dehors de la classe dans laquelle elle était définie.
Si Y n'est utilisé que dans X et ne sera jamais utilisé en dehors de X, je dirais de le garder là
Permettez-moi de vous donner un exemple de l'utilisation de classes imbriquées qui pourraient clarifier quand ce type d'architecture est approprié. J'ai récemment eu besoin de générer un tableau HTML en tirant les colonnes sélectionnées d'un tableau de données et en les "pivotant" pour que les lignes deviennent des colonnes et vice versa. Dans mon cas, il y avait deux opérations essentielles: pivoter les données et générer des sorties assez complexes (je ne montrais pas seulement les données: chaque colonne de données/ligne de tableau était soumise à des opérations d'extraction de titre, de génération de balises d'image, de mise en place de liens, etc., donc utiliser un SQL Pivot n'était pas vraiment correct non plus).
Après une tentative initiale de créer une classe pour faire le tout, j'ai reconnu qu'une grande partie des données/méthodes se répartissaient en trois partitions distinctes: le traitement des en-têtes, le traitement des lignes et le pivotement. Ainsi, j'ai décidé qu'une meilleure approche serait d'encapsuler la logique pour "en-tête" et "ligne" dans des classes imbriquées distinctes. Cela m'a permis de séparer les données détenues par chaque ligne et de programmer les opérations de pivot très proprement (en appelant un objet ligne distinct pour chaque colonne de votre tableau de données). À la fin des opérations de pivot, j'ai généré une sortie en appelant l'objet d'en-tête, puis chaque objet ligne à son tour pour générer sa sortie vers la classe principale.
Les classes séparées n'étaient pas appropriées car A) les classes imbriquées avaient besoin de certaines données de la classe principale et B) le traitement était très spécifique et inutile ailleurs. La simple programmation d'une grande classe était tout simplement plus compliquée en raison de la confusion entourant des termes tels que "colonne" et "ligne" qui différaient selon que vous parliez de données ou de sortie HTML. De plus, c'était un travail inhabituel dans la mesure où je générais du HTML dans ma classe affaires, donc je voulais séparer la logique métier pure de la génération d'interface utilisateur. Au final, les classes imbriquées offraient donc l'équilibre parfait entre encapsulation et partage de données.
Vous pouvez toujours refactoriser votre classe y dans un autre fichier, mais utilisez une classe parielle. L'avantage de ceci est que vous avez toujours une classe par fichier et que vous n'avez pas les problèmes de refactorisation pour déplacer la déclaration en dehors de la classe x.
par exemple. vous pourriez avoir un fichier de code: x.y.cs qui ressemblerait à quelque chose comme
partial class X
{
class Y
{
//implementation goes here
}
}