Quali sono le ragioni per cui si vorrebbe usare classi annidate?

In questa risposta StackOverflow un commentatore ha affermato che ” le classi private nidificate” possono essere piuttosto utili, quindi stavo leggendo su di esse in articoli come questo che tendono a spiegare come funzionano le classi annidate tecnicamente , ma non perché le usereste.

Suppongo che userò classi private annidate per piccole classi di helper che appartengono a una class più grande, ma spesso avrò bisogno di una class helper da un’altra class e quindi dovrò solo fare uno sforzo extra per (1) rendere la class nidificata non -nested o (2) renderlo pubblico e quindi accedervi con il prefisso della class esterna, che sembra essere un lavoro extra senza alcun valore aggiunto per avere la class nidificata in primo luogo. Quindi in generale non vedo un caso d’uso per le classi annidate , se non per mantenere le classi un po ‘più organizzate in gruppi, ma anch’io vado contro la chiarezza di una class per file che sono venuto a godere .

In che modo usi le classi annidate per rendere il tuo codice più gestibile, leggibile, efficiente?

Hai risposto alla tua stessa domanda. Usa le classi annidate quando hai bisogno di una class helper che non abbia senso al di fuori della class; in particolare quando la class nidificata può utilizzare i dettagli di implementazione privata della class esterna.

L’argomento secondo cui le classi nidificate sono inutili è anche un argomento secondo cui i metodi privati ​​sono inutili: un metodo privato potrebbe essere utile al di fuori della class, e quindi dovresti renderlo interno. Un metodo interno potrebbe essere utile al di fuori dell’assembly e pertanto lo renderebbe pubblico. Pertanto tutti i metodi dovrebbero essere pubblici. Se pensi che sia una brutta argomentazione, allora cosa c’è di diverso nel fatto che tu stia facendo lo stesso argomento per le classi invece dei metodi?

Faccio sempre le classi annidate perché sono spesso nella posizione di bisogno di incapsulare funzionalità in un helper che non ha senso al di fuori della class, e posso usare i dettagli di implementazione privata della class esterna. Ad esempio, scrivo compilatori. Recentemente ho scritto una class SemanticAnalyzer che fa un’analisi semantica degli alberi di analisi. Una delle sue classi nidificate è LocalScopeBuilder. In quali circostanze avrei bisogno di build un ambito locale quando non sto analizzando la semantica di un albero di analisi? Mai. Quella class è interamente un dettaglio di implementazione dell’analizzatore semantico. Ho in programma di aggiungere altre classi nidificate con nomi come NullableArithmeticAnalyzer e OverloadResolutionAnalyzer che non sono utili anche al di fuori della class, ma voglio incapsulare le regole del linguaggio in quelle classi specifiche.

Le persone usano anche classi nidificate per build cose come iteratori o comparatori: cose che non hanno senso al di fuori della class e sono esposte tramite un’interfaccia ben nota.

Un pattern che uso abbastanza frequentemente è quello di avere classi nidificate private che estendono la loro class esterna:

abstract public class BankAccount { private BankAccount() { } // Now no one else can extend BankAccount because a derived class // must be able to call a constructor, but all the constructors are // private! private sealed class ChequingAccount : BankAccount { ... } public static BankAccount MakeChequingAccount() { return new ChequingAccount(); } private sealed class SavingsAccount : BankAccount { ... } 

e così via. Le classi annidate funzionano molto bene con il modello di fabbrica. Qui BankAccount è una fabbrica per vari tipi di conti bancari, ognuno dei quali può utilizzare i dettagli di implementazione privata di BankAccount. Ma nessuna terza parte può creare il proprio tipo EvilBankAccount che estende BankAccount.

Restituzione di un’interfaccia al chiamante di cui si desidera hide l’implementazione.

 public class Outer { private class Inner : IEnumerable { /* Presumably this class contains some functionality which Outer needs * to access, but which shouldn't be visible to callers */ } public IEnumerable GetFoos() { return new Inner(); } } 

Le classi di helper private sono un buon esempio.

Ad esempio, oggetti di stato per i thread in background. Non vi è alcun motivo valido per esporre questi tipi. Definirli come tipi nidificati privati ​​sembra un modo abbastanza pulito per gestire il caso.

Li uso quando due valori associati (come in una tabella hash) non sono sufficienti internamente, ma sono abbastanza esternamente. Quindi creo una class nidificata con le proprietà che ho bisogno di archiviare e ne espongo solo alcune attraverso i metodi.

Penso che questo abbia senso, perché se nessun altro lo userà, perché creare una class esterna per questo? Semplicemente non ha senso.

Come per una class per file, è ansible creare classi parziali con la parola chiave partial , che è quello che faccio di solito.

Un esempio convincente che ho incontrato di recente è la class Node di molte strutture dati. Un Quadtree , ad esempio, deve sapere come memorizza i dati nei suoi nodes, ma a nessun’altra parte del codice dovrebbe interessare.

“classi nidificate private” possono essere abbastanza utili

Nota il privato in quella frase. I tipi nidificati pubblici non sono raccomandati.

E come la maggior parte delle risposte già nota: ogni class è un piccolo progetto software a sé stante. A volte diventa desiderabile arruolare classi di aiuto. Ma non è qualcosa a cui aspirare, non vuoi ancora che le lezioni diventino troppo grandi o troppo complesse.

Ho trovato alcuni casi in cui sono stati abbastanza utili:

  1. Gestione di uno stato privato complesso, ad esempio un Triangolo di interpolazione utilizzato da una class Interpolator. L’utente di Interpolator non ha bisogno di sapere che è implementato usando la triangolazione di Delauney e certamente non ha bisogno di conoscere i triangoli, quindi la struttura dei dati è una class nidificata privata.

  2. Come altri hanno menzionato, puoi esporre i dati utilizzati dalla class con un’interfaccia senza rivelare la piena implementazione di una class. Le classi nidificate possono anche accedere allo stato privato della class esterna, che consente di scrivere codice strettamente accoppiato senza esporre pubblicamente questo accoppiamento stretto (o anche internamente al resto dell’assieme).

  3. Mi sono imbattuto in alcuni casi in cui un framework si aspetta che una class derivi da qualche class base (come DependencyObject in WPF), ma vuoi che la tua class erediti da una base diversa. È ansible interagire con il framework utilizzando una class nidificata privata che discende dalla class base framework. Poiché la class nidificata può accedere allo stato privato (basta passarlo al genitore ‘this’ quando lo crei), puoi fondamentalmente usarlo per implementare l’ereditarietà di un povero uomo tramite la composizione.

Un modello molto comune in cui viene utilizzata questa tecnica è in scenari in cui una class restituisce un’interfaccia o un tipo di class base da una delle sue proprietà o metodi, ma il tipo concreto è una class nidificata privata. Considera il seguente esempio.

 public class MyCollection : IEnumerable { public IEnumerator GetEnumerator() { return new MyEnumerator(); } private class MyEnumerator { } } 

Di solito lo faccio quando ho bisogno di una combinazione di SRP (Principio di Responsabilità Unica) in determinate situazioni.

“Bene, se SRP è il tuo objective, perché non dividerli in classi diverse? ” Lo farai per l’80% delle volte, ma per quanto riguarda le situazioni in cui le classi che crei sono inutili per il mondo esterno? Non vuoi le lezioni che solo tu userai per ingombrare l’API del tuo assembly.

“Beh, non è quello che è internal ?” Sicuro. Per circa l’80% di questi casi. Ma che dire delle classi interne che devono accedere o modificare lo stato delle classi pubbliche? Ad esempio, quella class che è stata suddivisa in una o più classi interne per soddisfare la tua serie di SRP? Dovresti marcare anche tutti i metodi e le proprietà per l’uso da parte di queste classi internal .

“Cosa c’è che non va?” Niente. Per circa l’80% di questi casi. Certo, ora stai ingombrando l’interfaccia interna delle tue classi con metodi / proprietà che sono utili solo per quelle classi che hai creato in precedenza. E ora devi preoccuparti che altre persone della tua squadra che scrivono codice interno non rovinino il tuo stato usando quei metodi in modi che non ti aspettavi.

Le classi interne arrivano a modificare lo stato di ogni istanza del tipo in cui sono definite. Quindi, senza aggiungere membri alla definizione del tuo tipo, le tue classi interne possono lavorarci come necessario. Che, in circa 14 casi su 100, sarà la soluzione migliore per mantenere i tuoi tipi puliti, il tuo codice affidabile / gestibile e le tue responsabilità singolari.

Penso che altri abbiano coperto bene i casi d’uso per classi nidificate pubbliche e private.

Un punto che non ho visto è stato una risposta alla tua preoccupazione per una class per file . Puoi risolvere ciò rendendo parziale la class esterna e spostando la definizione della class interna in un file separato.

OuterClass.cs:

 namespace MyNameSpace { public partial class OuterClass { // main class members here // can use inner class } } 

OuterClass.Inner.cs:

 namespace MyNameSpace { public partial class OuterClass { private class Inner { // inner class members here } } } 

Potresti persino utilizzare l’nidificazione degli elementi di Visual Studio per rendere OuterClass.Inner.cs un “figlio” di OuterClass.cs, per evitare di ingombrare il tuo esploratore di soluzioni.

Sono davvero carini per, ad esempio, un’implementazione del modello singleton.

Ho un paio di posti in cui li sto usando per “aggiungere” anche il valore. Ho una casella combinata a selezione multipla in cui la mia class interna memorizza lo stato della casella di controllo e anche l’elemento dati. non c’è bisogno che il mondo sappia / usi questa class interna.

Le classi nidificate private anonime sono essenziali per i gestori di eventi nella GUI.

Se alcune classi non fanno parte dell’API, un’altra class esporta, deve essere resa privata. Altrimenti esponi più di quello che intendi. Il “bug da un milione di dollari” ne è un esempio. La maggior parte dei programmatori è troppo lenta per questo.

Peter

Se è necessario che un object restituisca alcune informazioni astratte sul suo stato, una class nidificata privata può essere idonea. Ad esempio, se un Fnord supporta i metodi “salva contesto” e “ripristina contesto”, può essere utile che la funzione “salva contesto” restituisca un object di tipo Fnord.SavedContext. Le regole di accesso ai tipi non sono sempre le più utili; ad esempio, sembra difficile consentire a Fnord di accedere a proprietà e metodi di un Fnord.SavedContext senza rendere tali proprietà e metodi visibili agli estranei. D’altra parte, si potrebbe avere Fnord.CreateSaveContext semplicemente creare un nuovo Fnord.SaveContext con il Fnord come parametro (poiché Fnord.SaveContext può accedere agli interni di Fnord), e Fnord.LoadContextFrom () può chiamare Fnord.SaveContext.RestoreContextTo ().

La domanda è codificata in C # quindi non sono sicuro che questo sia interessante, ma in COM è ansible utilizzare le classi interne per implementare interfacce quando una class C ++ implementa più interfacce COM … essenzialmente la si usa per la composizione piuttosto che per l’ereditarietà multipla.

Inoltre in MFC e forse in altre tecnologie potresti aver bisogno del tuo controllo / finestra di dialogo per avere una class drop-target, che ha poco senso se non come class nidificata.