Kiedy i dlaczego używać klas zagnieżdżonych?


30

Korzystając z programowania obiektowego mamy moc tworzenia klasy wewnątrz klasy (klasa zagnieżdżona), ale nigdy nie tworzyłem zagnieżdżonej klasy w ciągu 4 lat doświadczenia w programowaniu.
Do czego służą klasy zagnieżdżone?

Wiem, że klasa może być oznaczona jako prywatna, jeśli jest zagnieżdżona i że możemy uzyskać dostęp do wszystkich prywatnych członków tej klasy z klasy zawierającej. Możemy po prostu umieścić zmienne jako prywatne w samej klasie zawierającej.
Po co więc tworzyć klasę zagnieżdżoną?

W jakich scenariuszach należy zastosować klasy zagnieżdżone, czy są one bardziej wydajne pod względem wykorzystania niż inne techniki?


1
Masz kilka dobrych odpowiedzi, a ja czasami będę miał po prostu klasę robotniczą lub rozpórkę, której potrzebuję tylko w klasie.
paparazzo

Odpowiedzi:


19

Główną cechą zagnieżdżonych klas jest to, że mogą uzyskiwać dostęp do prywatnych członków klasy zewnętrznej, mając jednocześnie pełną moc samej klasy. Mogą być również prywatne, co pozwala na pewne dość potężne enkapsulacje w pewnych okolicznościach:

Tutaj zamykamy całkowicie seter do fabryki, ponieważ klasa jest prywatna, żaden konsument nie może go spuścić i uzyskać dostępu do setera, a my możemy całkowicie kontrolować, co jest dozwolone.

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

Poza tym jest przydatny do wdrażania interfejsów stron trzecich w kontrolowanym środowisku, w którym nadal możemy uzyskać dostęp do członków prywatnych.

Jeśli na przykład zapewnilibyśmy wystąpienie jakiegoś interfejsu do jakiegoś innego obiektu, ale nie chcemy, aby nasza klasa główna go implementowała, moglibyśmy pozwolić, aby klasa wewnętrzna go zaimplementowała.

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

W większości przypadków spodziewam się, że delegaci będą czystszym sposobem robienia tego, co pokazano w drugim przykładzie
Ben Aaronson

@BenAaronson jak zaimplementowałbyś przypadkowy interfejs za pomocą delegatów?
Esben Skov Pedersen

@EsbenSkovPedersen Cóż, na przykład, zamiast przekazać instancję Outer, przekazałbyś Func<int>, który byłby po prostu() => _example
Ben Aaronson

@BenAaronson w tym niezwykle prostym przypadku masz rację, ale w przypadku bardziej złożonych przykładów zbyt wielu delegatów staje się niezdarnych.
Esben Skov Pedersen

@EsbenSkovPedersen: Twój przykład ma pewne zalety, ale IMO powinno być używane tylko w przypadkach, w których tworzenie Innerzagnieżdżonych i internalnie działa (tj. Gdy nie masz do czynienia z różnymi zestawami). Uderzenie czytelności z klas zagnieżdżania sprawia, że ​​jest mniej korzystne niż używanie internal(w miarę możliwości).
Flater

23

Zazwyczaj zagnieżdżona klasa N jest tworzona wewnątrz klasy C, ilekroć C musi użyć czegoś wewnętrznego, co nigdy nie powinno (bezpośrednio) być używane poza C, i z jakiegokolwiek powodu, że coś musi być nowym typem obiektu, a nie jakimś istniejącym rodzaj.

Wierzę, że najczęściej zdarza się to przy implementacji metody, która zwraca obiekt implementujący jakiś interfejs, i chcemy ukryć konkretny typ tego obiektu, ponieważ nie będzie on przydatny nigdzie indziej.

Implementacja IEnumerable jest dobrym przykładem tego:

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

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

Po prostu nie ma powodu, aby ktokolwiek poza nim BlobOfBusinessDatawiedział lub troszczył się o konkretny BusinessDatumEnumeratortyp, więc równie dobrze możemy go zatrzymać w środku BlobOfBusinessData.

To nie miał być przykład „najlepszych praktyk”, jak IEnumerableprawidłowo wdrożyć , tylko absolutne minimum, aby zrealizować ten pomysł, więc pominąłem takie rzeczy, jak wyraźna IEnumerable.GetEnumerator()metoda.


6
Innym przykładem, którego używam z nowszymi programistami, jest Nodeklasa w LinkedList. Każdy, kto korzysta z LinkedListpliku, nie dba o sposób jego Nodeimplementacji, o ile ma dostęp do zawartości. Jedyną istotą, która w ogóle się przejmuje, jest LinkedListsama klasa.
Mag Xy

3

Po co więc tworzyć klasę zagnieżdżoną?

Mogę wymyślić kilka ważnych powodów:

1. Włącz enkapsulację

Wielokrotnie zagnieżdżone klasy są szczegółami implementacji klasy. Użytkownicy głównej klasy nie powinni martwić się o swoje istnienie. Powinieneś być w stanie je zmieniać do woli, bez konieczności zmiany kodu przez użytkowników klasy głównej.

2. Unikaj zanieczyszczenia nazwy

Nie należy dodawać typów, zmiennych, funkcji itp. W zakresie, chyba że są one odpowiednie w tym zakresie. Jest to nieco inne niż kapsułkowanie. Przydatne może być ujawnienie interfejsu typu zagnieżdżonego, ale właściwym miejscem dla typu zagnieżdżonego jest nadal główna klasa. W środowisku C ++ typy iteratorów są jednym z takich przykładów. Nie mam wystarczającego doświadczenia w języku C #, aby podać konkretne przykłady.

Stwórzmy uproszczony przykład, aby zilustrować, dlaczego przenoszenie zagnieżdżonej klasy do tego samego zakresu co główna klasa jest zanieczyszczeniem nazw. Załóżmy, że implementujesz klasę połączonych list. Normalnie byś użył

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

Jeśli zdecydujesz się przejść Nodedo tego samego zakresu co LinkedList, będziesz mieć

public class LinkedListNode
{
}

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

LinkedListNodejest mało prawdopodobne, aby był użyteczny bez LinkedListsamej klasy. Nawet jeśli LinkedListudostępniono pewne funkcje, które zwracają LinkedListNodeobiekt, z LinkedListktórego użytkownik może korzystać, nadal jest LinkedListNodeużyteczny tylko wtedy, gdy LinkedListjest używany. Z tego powodu uczynienie klasy „węzła” klasą równorzędną LinkedListzanieczyszcza zawarty zakres.


0
  1. Używam publicznych klas zagnieżdżonych do powiązanych klas pomocniczych.

    public class MyRecord {
        // stuff
        public class Comparer : IComparer<MyRecord> {
        }
        public class EqualsComparer : IEqualsComparer<MyRecord> {
        }
    }
    MyRecord[] array;
    Arrays.sort(array, new MyRecord.Comparer());
  2. Użyj ich do powiązanych odmian.

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

Dzwoniący może wybrać wersję odpowiednią dla danego problemu. Czasami klasy nie można w pełni skonstruować w jednym przebiegu i wymaga ona wersji modyfikowalnej. Jest to zawsze prawdziwe w przypadku obsługi danych cyklicznych. Zmienne można później przekształcić w tylko do odczytu.

  1. Używam ich do wewnętrznych zapisów

    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

Zagnieżdżonej klasy można użyć, gdy chcesz utworzyć więcej niż jedną instancję klasy lub gdy chcesz, aby ten typ był bardziej dostępny.

Klasa zagnieżdżona zwiększa enkapsulacje, a także prowadzi do bardziej czytelnego i łatwiejszego do utrzymania kodu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.