Quand et pourquoi utiliser des classes imbriquées?


29

En utilisant la programmation orientée objet, nous avons le pouvoir de créer une classe à l'intérieur d'une classe (une classe imbriquée), mais je n'ai jamais créé de classe imbriquée au cours de mes 4 années d'expérience en codage.
À quoi servent les classes imbriquées?

Je sais qu'une classe peut être marquée comme privée si elle est imbriquée et que nous pouvons accéder à tous les membres privés de cette classe à partir de la classe conteneur. Nous pourrions simplement mettre les variables comme privées dans la classe contenant elle-même.
Alors pourquoi créer une classe imbriquée?

Dans quels scénarios les classes imbriquées devraient-elles être utilisées ou sont-elles plus puissantes en termes d'utilisation que d'autres techniques?


1
Vous avez de bonnes réponses et j'ai parfois juste une classe ouvrière ou une jambe de force dont j'ai seulement besoin dans la classe.
paparazzo

Réponses:


19

La principale caractéristique des classes imbriquées est qu'elles peuvent accéder aux membres privés de la classe externe tout en ayant la pleine puissance d'une classe elle-même. Ils peuvent également être privés, ce qui permet une encapsulation assez puissante dans certaines circonstances:

Ici, nous verrouillons complètement le setter à l'usine puisque la classe est privée, aucun consommateur ne peut le downcaster et accéder au setter, et nous pouvons contrôler complètement ce qui est autorisé.

public interface IFoo 
{
    int Foo{get;}      
}
public class Factory
{
    private class MyFoo : IFoo
    {
        public int Foo{get;set;}
    }
    public IFoo CreateFoo(int value) => new MyFoo{Foo = value};
}

En dehors de cela, il est utile pour implémenter des interfaces tierces dans un environnement contrôlé où nous pouvons toujours accéder à des membres privés.

Si, par exemple, nous devions fournir une instance d'une interface à un autre objet mais que nous ne voulons pas que notre classe principale l'implémente, nous pourrions laisser une classe interne l'implémenter.

public class Outer
{
    private int _example;
    private class Inner : ISomeInterface
    {
        Outer _outer;
        public Inner(Outer outer){_outer = outer;}
        public int DoStuff() => _outer._example;
    }
    public void DoStuff(){_someDependency.DoBar(new Inner(this)); }
}

Dans la plupart des cas, je m'attendrais à ce que les délégués soient une façon plus propre de faire ce que vous montrez dans le deuxième exemple
Ben Aaronson

@BenAaronson comment implémenteriez-vous une interface aléatoire utilisant des délégués?
Esben Skov Pedersen

@EsbenSkovPedersen Eh bien pour votre exemple, au lieu de passer une instance de Outer, vous passeriez une Func<int>, qui serait juste() => _example
Ben Aaronson

@BenAaronson dans ce cas extrêmement simple, vous avez raison mais pour des exemples plus complexes, trop de délégués deviennent maladroits.
Esben Skov Pedersen

@EsbenSkovPedersen: Votre exemple a un certain mérite, mais IMO ne devrait être utilisé que dans les cas où la fabrication Innernon imbriquée internalne fonctionne pas (c'est-à-dire lorsque vous n'avez pas affaire à des assemblys différents). La lisibilité atteinte par les classes d'imbrication la rend moins favorable que l'utilisation internal(si possible).
Flater

23

En règle générale, une classe N imbriquée est créée à l'intérieur d'une classe C chaque fois que C doit utiliser quelque chose en interne qui ne devrait jamais être (directement) utilisé en dehors de C, et pour une raison quelconque, quelque chose doit être un nouveau type d'objet plutôt qu'un objet existant type.

Je crois que cela se produit le plus souvent en implémentant une méthode qui renvoie un objet implémentant une interface, et nous voulons garder le type concret de cet objet caché car il ne sera utile nulle part ailleurs.

L'implémentation d'IEnumerable en est un bon exemple:

class BlobOfBusinessData: IEnumerable<BusinessDatum>
{
    public IEnumerator<BusinessDatum> GetEnumerator()
    {
         return new BusinessDatumEnumerator(...);
    }

    class BusinessDatumEnumerator: IEnumerator<BusinessDatum>
    {
        ...
    }
}

Il n'y a tout simplement aucune raison pour que quelqu'un en dehors de BlobOfBusinessDataconnaître ou de se soucier du BusinessDatumEnumeratortype de béton , nous pourrions aussi bien le garder à l'intérieur BlobOfBusinessData.

Ce n'était pas censé être un exemple de "meilleures pratiques" sur la façon de mettre en œuvre IEnumerablecorrectement, juste le strict minimum pour faire passer l'idée, alors j'ai laissé de côté des choses comme une IEnumerable.GetEnumerator()méthode explicite .


6
Un autre exemple que j'utilise avec les nouveaux programmeurs est une Nodeclasse dans a LinkedList. Quiconque utilise le LinkedListne se soucie pas de la façon dont le Nodeest implémenté, tant qu'il peut accéder au contenu. La seule entité qui se soucie du tout est la LinkedListclasse elle-même.
Mage Xy

3

Alors pourquoi créer une classe imbriquée?

Je peux penser à quelques raisons importantes:

1. Activer l'encapsulation

Plusieurs fois, les classes imbriquées sont des détails d'implémentation de la classe. Les utilisateurs de la classe principale ne devraient pas avoir à se soucier de leur existence. Vous devriez pouvoir les modifier à volonté sans obliger les utilisateurs de la classe principale à changer leur code.

2. Évitez la pollution par les noms

Vous ne devez pas ajouter des types, des variables, des fonctions, etc. dans une étendue, sauf s'ils sont appropriés dans cette étendue. Ceci est légèrement différent de l'encapsulation. Il pourrait être utile d'exposer l'interface d'un type imbriqué, mais l'emplacement approprié pour le type imbriqué est toujours la classe principale. En terre C ++, les types d'itérateurs en sont un exemple. Je n'ai pas assez d'expérience en C # pour vous en donner des exemples concrets.

Faisons un exemple simpliste pour illustrer pourquoi le déplacement d'une classe imbriquée dans la même portée que la classe principale est une pollution de nom. Supposons que vous implémentez une classe de liste chaînée. Normalement, vous utiliseriez

publid class LinkedList
{
   class Node { ... }
   // Use Node to implement the LinkedList class.
}

Si vous décidez de passer Nodeà la même portée que LinkedList, vous aurez

public class LinkedListNode
{
}

public class LinkedList
{
  // Use LinkedListNode to implement the class
}

LinkedListNodeest peu susceptible d'être utile sans la LinkedListclasse elle-même. Même si LinkedListcertaines fonctions renvoyaient un LinkedListNodeobjet qu'un utilisateur LinkedListpourrait utiliser, elles ne sont LinkedListNodeutiles que lorsqu'elles LinkedListsont utilisées. Pour cette raison, faire de la classe "node" une classe homologue de LinkedListpollue la portée contenante.


0
  1. J'utilise des classes publiques imbriquées pour les classes d'assistance associées.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Utilisez-les pour les variantes associées.

    // Class that may or may not be mutable.
    public class MyRecord {
        protected string name;
        public virtual String Name { get => name; set => throw new InvalidOperation(); }
    
        public Mutable {
            public override String { get => name; set => name = value; }
        }
    }
    
    MyRecord mutableRecord = new MyRecord.Mutable();

L'appelant peut choisir quelle version convient à quel problème. Parfois, une classe ne peut pas être entièrement construite en un seul passage et nécessite une version mutable. Cela est toujours vrai lors de la manipulation de données cycliques. Les mutables peuvent être convertis en lecture seule ultérieurement.

  1. Je les utilise pour des enregistrements internes

    public class MyClass {
        List<Line> lines = new List<Line>();
    
        public void AddUser( string name, string address ) => lines.Add(new Line { Name = name, Address = address });
    
        class Line { string Name; string Address; }
    }

0

La classe imbriquée peut être utilisée chaque fois que vous souhaitez créer plusieurs instances de la classe ou chaque fois que vous souhaitez rendre ce type plus disponible.

La classe imbriquée augmente les encapsulations ainsi que cela conduira à un code plus lisible et maintenable.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.