2008-12-03 4 views
17

Angenommen, ich habe eine Klasse 'Anwendung'. Um initialisiert zu werden, benötigt es bestimmte Einstellungen im Konstruktor. Nehmen wir an, dass die Anzahl der Einstellungen so groß ist, dass es zwingend ist, sie in eine eigene Klasse zu stellen."Öffentliche" verschachtelte Klassen oder nicht

Vergleichen Sie die folgenden zwei Implementierungen dieses Szenarios.

Implementierung 1:

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

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

Implementierung 2:

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

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

Für mich ist der zweite Ansatz sehr viel besser. Es ist lesbarer, weil es die Beziehung zwischen den beiden Klassen stark betont. Wenn ich Code schreibe, um die Application-Klasse irgendwo zu instanziieren, wird der zweite Ansatz hübscher aussehen.

Nun stellen Sie sich vor, dass die Settings-Klasse selbst ihrerseits eine ähnlich "verwandte" Klasse hatte und diese Klasse wiederum auch. Gehen Sie nur drei solche Ebenen und die Klassenbenennung kommt im "nicht verschachtelten" Fall außer Kontrolle. Wenn Sie nisten, bleiben die Dinge dennoch elegant.

Trotz der oben genannten habe ich Leute gelesen, die auf StackOverflow sagen, dass verschachtelte Klassen nur dann gerechtfertigt sind, wenn sie für die Außenwelt nicht sichtbar sind; das heißt, wenn sie nur für die interne Implementierung der enthaltenden Klasse verwendet werden. Der häufig angeführte Einwand ist, die Größe der Quelldatei der enthaltenen Klasse aufzublähen, aber partielle Klassen sind die perfekte Lösung für dieses Problem.

Meine Frage ist, warum sind wir vorsichtig mit der "öffentlich exponierten" Verwendung von geschachtelten Klassen? Gibt es andere Argumente gegen eine solche Verwendung?

Antwort

22

Ich denke, es ist in Ordnung. Dies ist im Grunde das Builder-Muster und die Verwendung verschachtelter Klassen funktioniert ziemlich gut. Außerdem kann der Builder auf private Mitglieder der äußeren Klasse zugreifen, was sehr nützlich sein kann. Zum Beispiel können Sie eine Build-Methode auf dem Erbauer haben, die einen privaten Konstruktor auf der äußeren Klasse aufruft, die eine Instanz des Builder nimmt:

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

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

das, dass die nur Weg zum Aufbau einer Instanz der gewährleistet, äußere Klasse ist über den Erbauer.

Ich benutze ein Muster sehr ähnlich in meinem C# -Port der Protokollpuffer.

+0

Ich musste vor kurzem einen Builder-Code schreiben und erinnerte mich an diesen Beitrag. Es hat sich als sehr nützlich erwiesen, danke. –

+0

* "Erlaubt auch dem Builder Zugriff auf private Mitglieder der äußeren Klasse" * Nur mit einer expliziten Referenz, korrekt (ich bin mir ziemlich sicher, dass es nicht implizit ist wie es in den inneren Klassen von Java ist). – samosaris

+0

@SamusArin: Ja, es gibt keinen impliziten Verweis auf eine Instanz der enthaltenden Klasse. Sie haben aber auch Zugriff auf statische Mitglieder. Grundsätzlich habe ich * nur * über Zugänglichkeit gesprochen. –

0

Sie möchten vielleicht überprüfen, was Microsoft has to say zum Thema. Grundsätzlich ist es eine Frage des Stils, würde ich sagen.

+0

.NET erlegt die "eine (öffentliche) Klasse pro Datei" -Regel, die Java tut, nicht auf. Sie scheinen verschiedene Paradigmen für die Programmstruktur zu haben. WRT diese eine Regel allein, .NET hat mehr Flexibilität (ein ausdrucksstarkes Super-Set, wenn Sie so wollen). Großer Artikel übrigens. – samosaris

0

Ich verwende hauptsächlich geschachtelte Klassen für die Feinabstimmung des Zugriffs auf die geschachtelte und/oder die Container-Klasse.

Eine Sache zu erinnern ist, dass eine geschachtelte Klassendefinition im Grunde ein Klassenmitglied ist und Zugriff auf alle privaten Variablen des Containers haben wird.

Sie können dies auch verwenden, um die Verwendung einer bestimmten Klasse zu steuern.

Beispiel:

public abstract class Outer 
{ 
    protected class Inner 
    { 
    } 
} 

Nun, in diesem Fall kann der Benutzer (die Klasse) nur die innere Klasse zugreifen, wenn er Outer implementiert.

+1

Iff mag kein Tippfehler sein, aber es ist * etwas überflüssig, da Sie bereits das Wort "nur" im Satz haben. – phoog

5

Sie können Namespaces verwenden, um Dinge zu verknüpfen, die ... verwandt sind.

Zum Beispiel:

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

    public class Filling { } 
} 

Dies hat den Vorteil über Klassen, als ob sie Namespaces waren ist, dass Sie optional using auf der rufenden Seite verwenden können, um die Dinge abzukürzen:

using Diner; 

... 

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

Wenn Sie Verwenden Sie die Klasse Sandwich, als wäre es ein Namespace für Filling, müssen Sie den vollständigen Namen Sandwich.Filling verwenden, um auf Filling zu verweisen.

Und wie gehst du nachts schlafen, wenn du das weißt?

+2

Ich stimme der Präferenz für Namespaces zu, aber manchmal funktionieren sie nicht ganz. In dem betreffenden Beispiel subsumiert beispielsweise 'Anwendung' 'semantisch' 'Einstellungen', aber diese beiden in einen gemeinsamen Namensraum zu bringen würde diese Bedeutung nicht vermitteln. –

+0

** 'using Filling = Diner.Filling;' ** sollte "Referenzen" auf (nur) auflösen ** 'Filling' ** im Fall von" Klassen als Namespace ". – samosaris

0

Ein anderes praktisches Beispiel, das ich für eine gültige Verwendung von öffentlichen verschachtelten Klassen habe, ist im MVC-Muster, wenn ich ein Viewmodel mit einer IEnumerable-Eigenschaft verwende. zum Beispiel:

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

} 

Ich benutze es, weil ich nicht Product Klasse will außerhalb wiederverwendet werden, da es für dieses spezifische Ansichtsmodell nur besonders angefertigt wird, die es enthält. Aber ich kann es nicht privat machen, weil die Products-Eigenschaft öffentlich ist.