Wann und warum werden verschachtelte Klassen verwendet?


30

Mit der objektorientierten Programmierung können wir eine Klasse innerhalb einer Klasse (einer verschachtelten Klasse) erstellen, aber ich habe in meinen 4 Jahren Erfahrung im Codieren noch nie eine verschachtelte Klasse erstellt.
Wofür eignen sich verschachtelte Klassen?

Ich weiß, dass eine Klasse als privat markiert werden kann, wenn sie verschachtelt ist, und dass wir von der übergeordneten Klasse aus auf alle privaten Mitglieder dieser Klasse zugreifen können. Wir könnten die Variablen einfach als privat in die enthaltende Klasse selbst einfügen.
Warum also eine verschachtelte Klasse erstellen?

In welchen Szenarien sollten verschachtelte Klassen verwendet werden oder sind sie leistungsstärker als andere Techniken?


1
Sie haben einige gute Antworten und manchmal habe ich nur eine Arbeiterklasse oder eine Strebe, die ich nur innerhalb der Klasse brauche.
Paparazzo

Antworten:


19

Das Hauptmerkmal verschachtelter Klassen besteht darin, dass sie auf private Mitglieder der äußeren Klasse zugreifen können, während sie die volle Leistungsfähigkeit einer Klasse besitzen. Sie können auch privat sein, was unter bestimmten Umständen eine ziemlich leistungsfähige Kapselung ermöglicht:

Hier sperren wir den Setter vollständig auf die Fabrik, da die Klasse privat ist, und kein Verbraucher kann sie herunterschalten und auf den Setter zugreifen, und wir können vollständig steuern, was erlaubt ist.

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};
}

Ansonsten ist es nützlich, um Schnittstellen von Drittanbietern in einer kontrollierten Umgebung zu implementieren, in der wir weiterhin auf private Mitglieder zugreifen können.

Wenn wir zum Beispiel eine Instanz einer Schnittstelle für ein anderes Objekt bereitstellen, aber nicht möchten, dass unsere Hauptklasse es implementiert, können wir es von einer inneren Klasse implementieren lassen.

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)); }
}

In den meisten Fällen würde ich erwarten, dass die Delegierten eine sauberere Art und Weise sind, das zu tun, was Sie im zweiten Beispiel zeigen
Ben Aaronson,

@BenAaronson, wie würden Sie eine zufällige Schnittstelle mit Delegaten implementieren?
Esben Skov Pedersen

@EsbenSkovPedersen Gut für Ihr Beispiel, anstatt eine Instanz von zu übergeben Outer, würden Sie eine übergeben Func<int>, die nur lauten würde() => _example
Ben Aaronson

@BenAaronson In diesem extrem einfachen Fall haben Sie recht, aber für komplexere Beispiele werden zu viele Delegierte ungeschickt.
Esben Skov Pedersen

@EsbenSkovPedersen: Ihr Beispiel hat einige Vorteile, aber IMO sollte nur in Fällen verwendet werden, in denen es Innernicht geschachtelt ist und internalnicht funktioniert (dh wenn Sie nicht mit verschiedenen Assemblys arbeiten). Der Lesbarkeitsgewinn von Verschachtelungsklassen macht es weniger günstig als die Verwendung internal(wo möglich).
Flater

23

Normalerweise wird eine verschachtelte Klasse N in einer Klasse C erstellt, wenn C etwas intern verwenden muss, das niemals (direkt) außerhalb von C verwendet werden sollte, und aus welchem ​​Grund etwas ein neuer Objekttyp sein muss, anstatt eines vorhandenen Art.

Ich glaube, dass dies am häufigsten geschieht, wenn eine Methode implementiert wird, die ein Objekt zurückgibt, das eine Schnittstelle implementiert, und wir möchten den konkreten Typ dieses Objekts verbergen, da er an keiner anderen Stelle nützlich ist.

Die Implementierung von IEnumerable ist ein gutes Beispiel dafür:

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

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

Es gibt einfach keinen Grund für jemanden außerhalb BlobOfBusinessData, den Betontyp zu kennen oder sich darum zu kümmern BusinessDatumEnumerator, also können wir ihn genauso gut drinnen lassen BlobOfBusinessData.

Das sollte kein "Best-Practice" -Beispiel für die IEnumerableordnungsgemäße Implementierung sein , sondern nur das Nötigste, um die Idee zu vermitteln. Daher habe ich Dinge wie eine explizite IEnumerable.GetEnumerator()Methode weggelassen.


6
Ein weiteres Beispiel, das ich mit neueren Programmierern verwende, ist eine NodeKlasse in a LinkedList. Jedem, der LinkedListdas verwendet, ist es egal, wie das Nodeimplementiert wird, solange er auf die Inhalte zugreifen kann. Die einzige Entität, die sich überhaupt interessiert, ist die LinkedListKlasse selbst.
Magier Xy

3

Warum also eine verschachtelte Klasse erstellen?

Ich kann mir einige wichtige Gründe vorstellen:

1. Aktivieren Sie die Kapselung

Oft sind verschachtelte Klassen Implementierungsdetails der Klasse. Benutzer der Hauptklasse sollten sich nicht um ihre Existenz kümmern müssen. Sie sollten sie nach Belieben ändern können, ohne dass die Benutzer der Hauptklasse ihren Code ändern müssen.

2. Vermeiden Sie Namensverschmutzung

Sie sollten in einem Bereich keine Typen, Variablen, Funktionen usw. hinzufügen, es sei denn, sie sind für diesen Bereich geeignet. Dies unterscheidet sich geringfügig von der Verkapselung. Es könnte nützlich sein, die Schnittstelle eines verschachtelten Typs verfügbar zu machen, aber der richtige Platz für den verschachtelten Typ ist immer noch die Hauptklasse. In C ++ Land sind Iteratortypen ein solches Beispiel. Ich habe nicht genug Erfahrung in C #, um Ihnen konkrete Beispiele dafür zu geben.

Machen wir uns ein einfaches Beispiel, um zu veranschaulichen, warum das Verschieben einer verschachtelten Klasse in den gleichen Bereich wie die Hauptklasse Namensverschmutzung ist. Angenommen, Sie implementieren eine verknüpfte Listenklasse. Normalerweise würden Sie verwenden

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

Wenn Sie sich dazu entschließen, Nodein den gleichen Bereich wie zu wechseln LinkedList, haben Sie

public class LinkedListNode
{
}

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

LinkedListNodeist unwahrscheinlich, ohne LinkedListKlasse selbst nützlich zu sein . Selbst wenn LinkedListeinige Funktionen bereitgestellt werden, die ein LinkedListNodeObjekt zurückgeben, das ein Benutzer verwenden LinkedListkönnte, ist LinkedListNodedies nur dann sinnvoll, wenn LinkedListes verwendet wird. Aus diesem Grund LinkedListverschmutzt das Festlegen der "Knoten" -Klasse zu einer Peer-Klasse den enthaltenen Bereich.


0
  1. Ich verwende öffentliche verschachtelte Klassen für verwandte Hilfsklassen.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Verwenden Sie sie für verwandte Variationen.

    // 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();

Der Anrufer kann wählen, welche Version für welches Problem geeignet ist. Manchmal kann eine Klasse nicht in einem Durchgang vollständig erstellt werden und erfordert eine veränderbare Version. Dies gilt immer für den Umgang mit zyklischen Daten. Mutables können später in schreibgeschützte Dateien konvertiert werden.

  1. Ich benutze sie für interne Aufzeichnungen

    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

Geschachtelte Klasse kann verwendet werden, wenn Sie mehr als eine Instanz der Klasse erstellen oder wenn Sie diesen Typ verfügbarer machen möchten.

Verschachtelte Klassen erhöhen die Kapselung und führen zu besser lesbarem und wartbarem Code.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.