2008-12-03 9 views
17

Supposons que j'ai une classe "Application". Pour être initialisé, il prend certains paramètres dans le constructeur. Supposons également que le nombre de paramètres est si élevé qu'il est impératif de les placer dans une classe qui leur est propre.Classes imbriquées "publiques" ou non

Comparez les deux implémentations suivantes de ce scénario.

Application 1:

class Application 
{ 
    Application(ApplicationSettings settings) 
    { 
     //Do initialisation here 
    } 
} 

class ApplicationSettings 
{ 
    //Settings related methods and properties here 
} 

Mise en œuvre 2:

class Application 
{ 
    Application(Application.Settings settings) 
    { 
     //Do initialisation here 
    } 

    class Settings 
    { 
     //Settings related methods and properties here 
    } 
} 

Pour moi, la seconde approche est très bien préférable. Il est plus lisible car il souligne fortement la relation entre les deux classes. Lorsque j'écris du code pour instancier la classe Application n'importe où, la seconde approche va paraître plus jolie. Maintenant, imaginez simplement que la classe Settings elle-même avait à son tour une classe similaire "related" et cette classe l'a fait à son tour. Allez seulement trois niveaux de ce genre et la dénomination de la classe sort du champ dans le cas «non imbriqué». Si vous nichez, cependant, les choses restent élégantes.

Malgré ce qui précède, j'ai lu des gens dire sur StackOverflow que les classes imbriquées sont justifiées seulement si elles ne sont pas visibles au monde extérieur; c'est-à-dire si elles sont utilisées uniquement pour l'implémentation interne de la classe conteneur. L'objection couramment citée est de gonfler la taille du fichier source de la classe contenant, mais les classes partielles est la solution parfaite pour ce problème. Ma question est la suivante: pourquoi nous méfions-nous de l'utilisation «publique» des classes imbriquées? Y a-t-il d'autres arguments contre une telle utilisation?

Répondre

22

Je pense que c'est bien. Il s'agit essentiellement du modèle de générateur, et l'utilisation de classes imbriquées fonctionne plutôt bien. Il permet également au constructeur d'accéder aux membres privés de la classe externe, ce qui peut être très utile. Par exemple, vous pouvez avoir une méthode de construction sur le constructeur qui appelle un constructeur privé sur la classe externe qui prend une instance du constructeur:

public class Outer 
{ 
    private Outer(Builder builder) 
    { 
     // Copy stuff 
    } 

    public class Builder 
    { 
     public Outer Build() 
     { 
      return new Outer(this); 
     } 
    } 
} 

Cela garantit que le ne façon de construire une instance de la la classe externe est via le constructeur. J'utilise un motif très semblable à ceci dans mon port C# de Protocol Buffers.

+0

J'ai dû écrire du code de constructeur récemment et je me suis souvenu de ce post. Il s'est avéré être une approche très utile, alors merci. –

+0

* "Cela permet également au constructeur d'accéder aux membres privés de la classe externe" * Seulement avec une référence explicite, correcte (je suis sûr que ce n'est pas implicite comme dans les classes internes de Java). – samosaris

+0

@SamusArin: Oui, il n'y a pas de référence implicite à une instance de la classe conteneur. Mais vous avez également accès à des membres statiques. Fondamentalement, je parlais seulement de l'accessibilité. –

0

Vous voudrez peut-être vérifier ce que Microsoft has to say sur le sujet. Fondamentalement, c'est une question de style je dirais.

+0

.NET n'impose pas la règle "one (public) class per file" par Java. Ils semblent avoir des paradigmes distincts pour la structure du programme. WRT cette seule règle, .NET a plus de flexibilité (un super-jeu expressif si vous voulez). Grand article btw. – samosaris

0

J'utilise principalement des classes imbriquées pour affiner l'accès à la classe imbriquée et/ou conteneur. Une chose à retenir est qu'une définition de classe imbriquée est fondamentalement un membre de classe, et aura accès à toutes les variables privées du conteneur.

Vous pouvez également utiliser ceci pour contrôler l'utilisation d'une classe spécifique.

Exemple:

public abstract class Outer 
{ 
    protected class Inner 
    { 
    } 
} 

Maintenant, dans ce cas, l'utilisateur (de classe) ne peut accéder à la classe intérieure, s'il met en œuvre extérieur.

+1

Iff peut ne pas être une faute de frappe, mais il * est * quelque peu redondant puisque vous avez déjà le mot "seulement" dans la phrase. – phoog

5

Vous pouvez utiliser des espaces de noms pour relier des choses qui sont liées.

Par exemple:

namespace Diner 
{ 
    public class Sandwich 
    { 
     public Sandwich(Filling filling) { } 
    } 

    public class Filling { } 
} 

L'avantage de ce cours en utilisant des classes comme si elles étaient des espaces de noms est que vous pouvez éventuellement utiliser using sur le côté appelant à abrégez choses:

using Diner; 

... 

var sandwich = new Sandwich(new Filling()); 

Si vous utilisez la classe Sandwich comme s'il s'agissait d'un espace de nom pour Filling, vous devez utiliser le nom complet Sandwich.Filling pour faire référence à Filling.

Et comment allez-vous dormir la nuit en sachant cela?

+2

Je suis d'accord avec la préférence pour les espaces de noms, mais parfois ils ne fonctionnent pas tout à fait. Dans l'exemple en question, par exemple, sémantiquement, 'Application' subsume 'Settings', mais les placer tous les deux dans un espace de noms commun ne traduirait pas ce sens. –

+0

** 'using Filling = Diner.Filling; ** devrait résoudre" references "à (juste) **' Filling' ** dans le cas de "classes comme espace de noms". – samosaris

0

Un autre exemple pratique que j'ai pour une utilisation valide des classes imbriquées publiques est dans le modèle MVC lorsque j'utilise un viewmodel avec une propriété IEnumerable. par exemple:

public class OrderViewModel 
{ 
public int OrderId{ get; set; } 
public IEnumerable<Product> Products{ get; set; } 

public class Product { 
public string ProductName{ get; set; } 
public decimal ProductPrice{ get; set; } 
} 

} 

Je l'utilise parce que je ne veux pas Product classe pour être réutilisée en dehors car il est personnalisé que pour cette viewmodel spécifique qui le contient. Mais je ne peux pas le rendre privé car la propriété Products est publique.

Questions connexes