Quando e perché utilizzare le classi nidificate?


29

Usando la programmazione orientata agli oggetti abbiamo il potere di creare una classe all'interno di una classe (una classe nidificata), ma non ho mai creato una classe nidificata nei miei 4 anni di esperienza di programmazione.
A cosa servono le classi nidificate?

So che una classe può essere contrassegnata come privata se è nidificata e che possiamo accedere a tutti i membri privati ​​di quella classe dalla classe contenente. Potremmo semplicemente mettere le variabili come private nella classe contenitrice stessa.
Quindi perché creare una classe nidificata?

In quali scenari devono essere utilizzate le classi nidificate o sono più potenti in termini di utilizzo rispetto ad altre tecniche?


1
Hai delle buone risposte e a volte avrò solo una classe lavoratrice o un montante di cui ho solo bisogno all'interno della classe.
paparazzo,

Risposte:


19

La caratteristica principale delle classi nidificate è che possono accedere ai membri privati ​​della classe esterna pur avendo il pieno potere di una classe stessa. Inoltre possono essere privati, il che consente un incapsulamento piuttosto potente in determinate circostanze:

Qui blocciamo il setter completamente in fabbrica poiché la classe è privata, nessun consumatore può scaricarlo e accedere al setter e possiamo controllare completamente ciò che è permesso.

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

Oltre a ciò, è utile per l'implementazione di interfacce di terze parti in un ambiente controllato in cui possiamo ancora accedere ai membri privati.

Se, ad esempio, dovessimo fornire un'istanza di qualche interfaccia a qualche altro oggetto, ma non vogliamo che la nostra classe principale la implementi, potremmo permettere a una classe interna di implementarla.

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

Nella maggior parte dei casi mi aspetto che i delegati siano un modo più pulito di fare ciò che stai mostrando nel secondo esempio
Ben Aaronson,

@BenAaronson come implementeresti un'interfaccia casuale usando i delegati?
Esben Skov Pedersen,

@EsbenSkovPedersen Bene per il tuo esempio, invece di passare un'istanza di Outer, passeresti a Func<int>, che sarebbe solo() => _example
Ben Aaronson

@BenAaronson in questo caso estremamente semplice hai ragione, ma per esempi più complessi, troppi delegati diventano goffi.
Esben Skov Pedersen,

@EsbenSkovPedersen: il tuo esempio ha qualche merito, ma IMO dovrebbe essere usato solo nei casi in cui la creazione Innernon nidificata e internalnon funziona (ovvero quando non hai a che fare con assiemi diversi). Il colpo di leggibilità delle classi di nidificazione lo rende meno favorevole rispetto all'utilizzo internal(ove possibile).
Flater

23

In genere, una classe nidificata N viene creata all'interno di una classe C ogni volta che C deve utilizzare qualcosa internamente che non dovrebbe mai essere (direttamente) usato al di fuori di C, e per qualsiasi motivo che qualcosa debba essere un nuovo tipo di oggetto piuttosto che alcuni esistenti genere.

Credo che ciò accada molto spesso implementando un metodo che restituisce un oggetto che implementa alcune interfacce e vogliamo mantenere nascosto il tipo concreto di quell'oggetto perché non sarà utile da nessun'altra parte.

L'implementazione di IEnumerable è un buon esempio di questo:

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

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

Semplicemente non c'è motivo per nessuno al di fuori di BlobOfBusinessDataconoscere o preoccuparsi del BusinessDatumEnumeratortipo concreto , quindi potremmo anche tenerlo dentro BlobOfBusinessData.

Questo non voleva essere un esempio di "buone pratiche" su come implementare IEnumerablecorrettamente, solo il minimo indispensabile per far passare l'idea, quindi ho lasciato fuori cose come un IEnumerable.GetEnumerator()metodo esplicito .


6
Un altro esempio che utilizzo con i programmatori più recenti è una Nodeclasse in a LinkedList. Chiunque usi il LinkedListnon si preoccupa di come Nodeviene implementato, purché possano accedere ai contenuti. L'unica entità che si prende cura di tutti è la LinkedListclasse stessa.
Mago Xy,

3

Quindi perché creare una classe nidificata?

Posso pensare a un paio di ragioni importanti:

1. Abilita incapsulamento

Molte volte le classi nidificate sono dettagli di implementazione della classe. Gli utenti della classe principale non dovrebbero preoccuparsi della loro esistenza. Dovresti essere in grado di cambiarli a piacimento senza richiedere agli utenti della classe principale di cambiare il loro codice.

2. Evitare l'inquinamento dei nomi

Non aggiungere tipi, variabili, funzioni, ecc. In un ambito a meno che non siano appropriati in tale ambito. Questo è leggermente diverso dall'incapsulamento. Potrebbe essere utile esporre l'interfaccia di un tipo nidificato, ma il posto corretto per il tipo nidificato è ancora la classe principale. In C ++ land, i tipi di iteratori ne sono un esempio. Non ho abbastanza esperienza in C # per darvi esempi concreti.

Facciamo un esempio semplicistico per illustrare perché spostare una classe nidificata nello stesso ambito della classe principale è l'inquinamento dei nomi. Supponiamo che tu stia implementando una classe di elenco collegata. Normalmente, useresti

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

Se decidi di passare Nodeallo stesso ambito LinkedList, avrai

public class LinkedListNode
{
}

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

LinkedListNodeè improbabile che sia utile senza la LinkedListclasse stessa. Anche se LinkedListfornito alcune funzioni che restituiscono un LinkedListNodeoggetto che un utente LinkedListpotrebbe utilizzare, è comunque LinkedListNodeutile solo quando LinkedListviene utilizzato. Per tale motivo, rendere la classe "nodo" una classe pari LinkedListè inquinare l'ambito di contenimento.


0
  1. Uso le classi nidificate pubbliche per le classi helper correlate.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Usali per le variazioni correlate.

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

Il chiamante può scegliere quale versione è adatta a quale problema. A volte una classe non può essere completamente costruita in un passaggio e richiede una versione mutabile. Questo è sempre vero quando si gestiscono dati ciclici. I mutabili possono essere convertiti in sola lettura in seguito.

  1. Li uso per i record interni

    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

La classe nidificata può essere utilizzata ogni volta che si desidera creare più di un'istanza della classe o quando si desidera rendere tale tipo più disponibile.

La classe nidificata aumenta gli incapsulamenti e porterà a un codice più leggibile e gestibile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.