Wann solltest du eine private / innere Klasse benutzen?

21

Um zu verdeutlichen, was ich frage ist

public class A{
    private/*or public*/ B b;
}

gegen

public class A{
    private/*or public*/ class B{
        ....
    }
}

Ich kann mir auf jeden Fall einige Gründe vorstellen, um den einen oder anderen zu verwenden, aber was ich wirklich sehen möchte, sind überzeugende Beispiele, die zeigen, dass die Vor- und Nachteile nicht nur akademisch sind.

EpsilonVector
quelle
2
Die Antwort würde von den Einrichtungen abhängen, die die Sprache und die Kernbibliotheken bieten. Unter der Annahme, dass C # die Sprache ist, versuchen Sie, diese mit StyleCop auszuführen. Ich bin mir ziemlich sicher, dass Sie eine Warnung erhalten, wenn Sie diese Klasse in eine eigene Datei aufteilen. Das bedeutet, dass genügend Leute bei MSFT der Meinung sind, dass Sie keines der Beispiele verwenden müssen, die Sie verwendet haben.
Job
Was wäre ein überzeugendes Beispiel, wenn akademische Beispiele nicht gut genug wären?
@Job StyleCop gibt nur dann eine Warnung aus, wenn die inneren Klassen von außen sichtbar sind. Wenn es privat ist, ist es völlig in Ordnung und hat tatsächlich viele Verwendungen.
Julealgon

Antworten:

20

Sie werden normalerweise verwendet, wenn eine Klasse ein internes Implementierungsdetail einer anderen Klasse ist und nicht Teil ihrer externen Schnittstelle. Ich habe sie meistens als reine Datenklassen angesehen, die die internen Datenstrukturen in Sprachen, die keine separate Syntax für Strukturen im C-Stil haben, sauberer machen. Sie sind manchmal auch nützlich für stark angepasste, von einer Methode abgeleitete Objekte wie Ereignishandler oder Arbeitsthreads.

Karl Bielefeldt
quelle
2
So benutze ich sie. Wenn eine Klasse aus funktionaler Sicht nichts anderes als ein privates Implementierungsdetail einer anderen Klasse ist, sollte sie genau wie eine private Methode oder ein privates Feld so deklariert werden. Kein Grund, den Namespace mit Müll zu verschmutzen, der sonst nirgends verwendet wird.
Aaronaught
9

Angenommen, Sie erstellen einen Baum, eine Liste, ein Diagramm usw. Warum sollten Sie die internen Details eines Knotens oder einer Zelle der Außenwelt aussetzen?

Wenn Sie ein Diagramm oder eine Liste verwenden, sollten Sie sich nur auf die Benutzeroberfläche und nicht auf die Implementierung verlassen, da Sie sie möglicherweise eines Tages in der Zukunft ändern möchten (z. B. von einer Array-basierten auf eine Pointer-basierte Implementierung) und auf die Clients, die Ihre verwenden Die Datenstruktur ( jeder von ihnen ) müsste ihren Code ändern, um ihn an die neue Implementierung anzupassen.

Wenn Sie stattdessen die Implementierung eines Knotens oder einer Zelle in eine private innere Klasse einkapseln, können Sie die Implementierung jederzeit ändern, ohne dass die Clients gezwungen sind, ihren Code entsprechend anzupassen, solange die Schnittstelle Ihrer Datenstruktur erhalten bleibt unberührt.

Das Ausblenden der Details der Implementierung Ihrer Datenstruktur führt auch zu Sicherheitsvorteilen, da Sie, wenn Sie Ihre Klasse verteilen möchten, nur die Schnittstellendatei zusammen mit der kompilierten Implementierungsdatei zur Verfügung stellen und niemand weiß, ob Sie tatsächlich Arrays oder Zeiger verwenden für Ihre Implementierung, um Ihre Anwendung vor einer Ausbeutung oder zumindest vor Wissen zu schützen, da es unmöglich ist, Ihren Code zu missbrauchen oder zu überprüfen. Bitte unterschätzen Sie neben praktischen Aspekten nicht, dass es sich in solchen Fällen um eine äußerst elegante Lösung handelt.

Nicola Lamonaca
quelle
5

Meiner Meinung nach ist es ein fairer Cop, intern definierte Klassen für Klassen zu verwenden, die kurz in einer Klasse verwendet werden, um eine bestimmte Aufgabe zu ermöglichen.

Wenn Sie beispielsweise eine Datenliste binden müssen, die aus zwei unabhängigen Klassen besteht:

public class UiLayer
{
    public BindLists(List<A> as, List<B> bs)
    {
        var list = as.ZipWith(bs, (x, y) => new LayerListItem { AnA = x, AB = y});
        // do stuff with your new list.
    }

    private class LayerListItem
    {
        public A AnA;
        public B AB;
    }
}

Wenn Ihre interne Klasse von einer anderen Klasse verwendet wird, sollten Sie sie separat platzieren. Wenn Ihre interne Klasse eine Logik enthält, sollten Sie sie separat platzieren.

Grundsätzlich finde ich sie großartig, um Lücken in Ihren Datenobjekten zu schließen, aber sie sind schwierig zu pflegen, wenn sie tatsächlich Logik enthalten müssen, da Sie wissen müssen, wo Sie danach suchen müssen, wenn Sie sie ändern müssen.

Ed James
quelle
2
Angenommen, C #, können Sie hier nicht einfach Tupel verwenden?
Job
3
@Job Klar, aber manchmal tuple.ItemXmacht das den Code unübersichtlich.
Lasse Espeholt
2
@Job yup, lasseespeholt hat das richtig gemacht. Ich möchte lieber eine innere Klasse von {string Title; Zeichenfolge Beschreibung; } als ein Tupel <string, string>.
Ed James
Wäre es an diesem Punkt nicht sinnvoll, diese Klasse in eine eigene Datei zu schreiben?
Job
Nein, nicht, wenn Sie es nur wirklich verwenden, um einige Daten kurz zusammenzufügen, um eine Aufgabe zu erreichen, die nicht außerhalb des Zuständigkeitsbereichs der übergeordneten Klasse liegt. Zumindest meiner Meinung nach nicht!
Ed James
4
  • Innere Klassen in einer bestimmten Sprache (z. B. Java) sind an ein Objekt ihrer übergeordneten Klasse gebunden und können ihre Mitglieder verwenden, ohne sie zu qualifizieren. Wenn verfügbar, ist es klarer als eine Neuimplementierung mit anderen Sprachfunktionen.

  • Geschachtelte Klassen in einer anderen Sprache (z. B. C ++) haben keine solche Bindung. Sie verwenden einfach die von der Klasse bereitgestellte Bereichs- und Zugriffskontrolle.

Ein Programmierer
quelle
3
Eigentlich hat Java beides .
Joachim Sauer
3

Ich vermeide innere Klassen in Java, da das Austauschen im laufenden Betrieb nicht funktioniert, wenn innere Klassen vorhanden sind. Ich hasse das, weil ich es wirklich hasse, den Code für solche Überlegungen zu kompromittieren, aber diese Tomcat-Neustarts summieren sich.

Kevin Cline
quelle
Ist dieses Problem irgendwo dokumentiert?
gnat
Downvoter: Ist meine Behauptung falsch?
Kevin Cline
1
Ich habe das Abstimmen abgelehnt, weil Ihre Antwort keine Details enthält, nicht, weil sie falsch ist. Mangelnde Details erschweren die Überprüfung Ihrer Behauptung. Wenn Sie mehr zu diesem Thema hinzufügen, würde ich wahrscheinlich upvote zurückkehren, weil Ihre Informationen sehen ziemlich interessant und nützlich sein
gnat
1
@gnat: Ich habe ein wenig recherchiert und obwohl dies eine wohlbekannte Wahrheit zu sein scheint, habe ich keinen endgültigen Hinweis auf die Einschränkungen von Java HotSwap gefunden.
Kevin Cline
Wir sind nicht auf Skeptics.SE :) nur ein paar Hinweise, es muss nicht definitiv sein
Mücke
2

Eine der Anwendungen für die private statische innere Klasse ist das Memo-Muster. Sie ordnen alle Daten, die gespeichert werden müssen, einer privaten Klasse zu und geben sie von einer Funktion als Objekt zurück. Niemand außerhalb kann es (ohne Reflektion, Deserialisierung, Speicherinspektion ...) untersuchen / modifizieren, aber er kann es Ihrer Klasse zurückgeben und es kann seinen Zustand wiederherstellen.

user470365
quelle
2

Ein Grund für die Verwendung einer privaten inneren Klasse könnte sein, dass für eine von Ihnen verwendete API die Vererbung einer bestimmten Klasse erforderlich ist, Sie jedoch das Wissen dieser Klasse nicht an Benutzer Ihrer äußeren Klasse exportieren möchten. Beispielsweise:

// async_op.h -- someone else's api.
struct callback
{
    virtual ~callback(){}
    virtual void fire() = 0;
};

void do_my_async_op(callback *p);



// caller.cpp -- my api
class caller
{
private :
    struct caller_inner : callback
    {
        caller_inner(caller &self) : self_(self) {}
        void fire() { self_.do_callback(); }
        caller &self_;
    };

    void do_callback()
    {
        // Real callback
    }

    caller_inner inner_;

public :
    caller() : inner_(*this) {}

    void do_op()
    {
        do_my_async_op(&inner_);
    }
};

In diesem Fall erfordert do_my_async_op, dass ein Objekt vom Typ Rückruf an dieses Objekt übergeben wird. Dieser Rückruf hat eine Signatur einer öffentlichen Memberfunktion, die von der API verwendet wird.

Wenn do_op () von der äußeren Klasse aufgerufen wird, verwendet es eine Instanz der privaten inneren Klasse, die von der erforderlichen Rückrufklasse erbt, anstatt eines Zeigers auf sich selbst. Die äußere Klasse hat einen Verweis auf die äußere Klasse, um den Rückruf einfach in die private Memberfunktion do_callback der äußeren Klasse umzuleiten.

Dies hat den Vorteil, dass Sie sicher sind, dass niemand die öffentliche "fire ()" - Memberfunktion aufrufen kann.

Kaz Dragon
quelle
2

Laut Jon Skeet in C # in Depth war die Verwendung einer verschachtelten Klasse die einzige Möglichkeit, einen vollständig faulen thread-sicheren Singleton (siehe Fünfte Version) (bis .NET 4) zu implementieren .

Scott Whitlock
quelle
1
es ist Initialisierung On Demand Halter Idiom , in Java es erfordert auch private statische innere Klasse. Es ist in empfohlen JMM FAQ von Brian Goetz in JCiP 16,4 und von Joshua Bloch in JavaOne 2008 Mehr Effective Java (pdf) "für High-Performance auf einem statisches Feld ..."
gnat
1

Private und innere Klassen werden verwendet, um den Kapselungsgrad zu erhöhen und Implementierungsdetails zu verbergen.

In C ++ kann das gleiche Konzept zusätzlich zu einer privaten Klasse erreicht werden, indem der anonyme Namespace einer Klasse cpp implementiert wird. Dies dient zum Verbergen / Privatisieren eines Implementierungsdetails.

Es ist die gleiche Idee wie eine innere oder private Klasse, jedoch auf einer noch größeren Ebene der Kapselung, da es außerhalb der Kompilierungseinheit der Datei vollständig unsichtbar ist. Nichts davon erscheint in der Header-Datei oder ist in der Deklaration der Klasse äußerlich sichtbar.

hiwaylon
quelle
Irgendwelche konstruktiven Rückmeldungen zum -1?
Hiwaylon
1
Ich habe nicht abgelehnt, aber ich würde vermuten, dass Sie die Frage nicht wirklich beantworten: Sie geben keinen Grund an, private / innere Klasse zu verwenden. Sie beschreiben nur, wie es funktioniert und wie man etwas Ähnliches in c ++ macht
Simon Bergot
Tatsächlich. Ich dachte, dass es anhand meiner und der anderen Antworten (Ausblenden von Implementierungsdetails, Erhöhen des Kapselungsgrades usw.) offensichtlich war, aber ich werde versuchen, einige zu klären. Vielen Dank @ Simon.
Hiwaylon
1
Nizza bearbeiten. Und beleidigen Sie sich bitte nicht wegen dieser Art von Reaktion. Das SO-Netzwerk versucht, Richtlinien zur Verbesserung des Signal-Rausch-Verhältnisses durchzusetzen. Einige Leute sind ziemlich streng in Bezug auf den Umfang der Fragen / Antworten und vergessen manchmal, nett zu sein (indem sie zum Beispiel ihre Ablehnung kommentieren)
Simon Bergot
@ Simon Danke. Es ist enttäuschend, weil es eine schlechte Kommunikationsqualität aufweist. Vielleicht ist es ein bisschen zu einfach, hinter dem anonymen Schleier des Webs "streng" zu sein? Na ja, nichts Neues, denke ich. Nochmals vielen Dank für das positive Feedback.
Hiwaylon