web-dev-qa-db-fra.com

Quand et pourquoi utiliser des classes imbriquées?

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?

36
mayur rathi

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 nous, par exemple, 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)); }
}
20

En règle générale, une classe imbriquée N est créée à l'intérieur d'une classe C chaque fois que C a besoin d'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 quiconque en dehors de BlobOfBusinessData de connaître ou de se soucier du type concret BusinessDatumEnumerator, nous pouvons donc aussi le garder à l'intérieur BlobOfBusinessData.

Ce n'était pas censé être un exemple de "meilleures pratiques" sur la façon d'implémenter IEnumerable correctement, juste le strict minimum pour faire passer l'idée, alors j'ai omis des choses comme une IEnumerable.GetEnumerator() explicite _ méthode.

25
Ixrec

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 la 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 déplacer Node dans la même portée que LinkedList, vous aurez

public class LinkedListNode
{
}

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

LinkedListNode ne sera probablement pas utile sans LinkedList classe elle-même. Même si LinkedList fournissait certaines fonctions qui retournent un objet LinkedListNode qu'un utilisateur de LinkedList pourrait utiliser, cela ne rendra LinkedListNode utile que lorsque LinkedList est utilisé. Pour cette raison, faire de la classe "node" une classe homologue de LinkedList pollue la portée contenante.

4
R Sahu
  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
Tag

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

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

0
Ishan Shah