Kiedy należy użyć klasy prywatnej / wewnętrznej?

21

Aby wyjaśnić, pytam o to

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

vs.

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

Zdecydowanie mogę wymyślić kilka powodów, aby użyć jednego lub drugiego, ale tak naprawdę chciałbym zobaczyć przekonujące przykłady, które pokazują, że zalety i wady są nie tylko akademickie.

EpsilonVector
źródło
2
Odpowiedź zależy od udogodnień zapewnianych przez język i biblioteki podstawowe. Zakładając język C #, spróbuj uruchomić je przez StyleCop. Jestem pewien, że dostaniesz ostrzeżenie o rozdzieleniu tej klasy na własny plik. Oznacza to, że wystarczająca liczba osób w firmie MSFT uważa, że ​​nie trzeba używać żadnego z użytych przykładów.
Job
Jaki byłby przekonujący przykład, gdyby przykłady akademickie nie były wystarczająco dobre?
@Job StyleCop wyświetli ostrzeżenie tylko wtedy, gdy klasy wewnętrzne są widoczne z zewnątrz. Jeśli jest prywatny, jest całkowicie w porządku i ma wiele zastosowań.
julealgon

Odpowiedzi:

20

Są zwykle używane, gdy klasa jest wewnętrznym szczegółem implementacyjnym innej klasy, a nie częścią jej zewnętrznego interfejsu. Widziałem je głównie jako klasy tylko danych, które czyszczą wewnętrzne struktury danych w językach, które nie mają osobnej składni dla struktur typu c. Przydają się także czasami w przypadku wysoce spersonalizowanych obiektów pochodzących z jednej metody, takich jak procedury obsługi zdarzeń lub wątki robocze.

Karl Bielefeldt
źródło
2
Tak ich używam. Jeśli klasa jest z funkcjonalnego punktu widzenia niczym innym, jak szczegółem prywatnej implementacji innej klasy, to powinna być zadeklarowana w ten sposób, tak jak prywatna metoda lub pole. Nie ma powodu, aby zanieczyszczać przestrzeń nazw śmieciami, które nigdy nie zostaną wykorzystane w innym miejscu.
Aaronaught
9

Załóżmy, że budujesz drzewo, listę, wykres i tak dalej. Dlaczego powinieneś ujawniać wewnętrzne szczegóły węzła lub komórki światu zewnętrznemu?

Każdy, kto korzysta z wykresu lub listy, powinien polegać tylko na jego interfejsie, a nie na implementacji, ponieważ możesz chcieć go zmienić w przyszłości (np. Z implementacji tablicowej na opartą na wskaźnikach), a klienci korzystający z Twojej struktura danych ( każda z nich ) musiałaby zmodyfikować swój kod, aby dostosować go do nowej implementacji.

Zamiast tego hermetyzacja implementacji węzła lub komórki w prywatnej klasie wewnętrznej daje swobodę modyfikowania implementacji w dowolnym momencie, bez konieczności zmuszania klientów do dostosowywania swojego kodu, o ile interfejs Twojej struktury danych pozostaje niezmieniony nietknięty.

Ukrywanie szczegółów implementacji struktury danych prowadzi również do korzyści bezpieczeństwa, ponieważ jeśli chcesz rozpowszechniać swoją klasę, udostępnisz tylko plik interfejsu wraz ze skompilowanym plikiem implementacji i nikt nie będzie wiedział, czy faktycznie używasz tablic lub wskaźników za wdrożenie, chroniąc w ten sposób aplikację przed jakimś wykorzystaniem lub przynajmniej wiedzą z powodu niemożności niewłaściwego użycia lub sprawdzenia kodu. Oprócz kwestii praktycznych nie należy lekceważyć faktu, że w takich przypadkach jest to niezwykle eleganckie rozwiązanie.

Nicola Lamonaca
źródło
5

Moim zdaniem uczciwy gliniarz używa klas zdefiniowanych wewnętrznie dla klas, które są krótko używane w klasie, aby umożliwić określone zadanie.

Na przykład, jeśli chcesz powiązać listę danych składającą się z dwóch niepowiązanych klas:

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

Jeśli twoja klasa wewnętrzna jest używana przez inną klasę, powinieneś ją rozdzielić. Jeśli twoja klasa wewnętrzna zawiera jakąś logikę, powinieneś ją rozdzielić.

Zasadniczo uważam, że świetnie nadają się do zaślepiania otworów w twoich obiektach danych, ale są trudne do utrzymania, jeśli muszą zawierać logikę, ponieważ musisz wiedzieć, gdzie ich szukać, jeśli chcesz to zmienić.

Ed James
źródło
2
Zakładając C #, nie możesz po prostu użyć krotek tutaj?
Job
3
@ Job Pewnie, ale czasami tuple.ItemXkod jest niejasny.
Lasse Espeholt
2
@Jak tak, Lasseespeholt dobrze to zrozumiał. Wolałbym zobaczyć wewnętrzną klasę {string Title; ciąg Opis; } niż krotek <ciąg, ciąg>.
Ed James
czy w tym momencie nie ma sensu umieszczać tej klasy we własnym pliku?
Job
Nie, nie jeśli naprawdę używasz go tylko do krótkiego powiązania niektórych danych w celu osiągnięcia zadania, które nie wykracza poza zadania klasy nadrzędnej. Przynajmniej nie moim zdaniem!
Ed James
4
  • Klasy wewnętrzne w jakimś języku (na przykład Java) mają powiązanie z obiektem ich zawierającej klasy i mogą używać swoich członków bez ich kwalifikowania. Jeśli są dostępne, jest bardziej przejrzyste niż ich ponowna implementacja przy użyciu innych narzędzi językowych.

  • Klasy zagnieżdżone w innym języku (na przykład C ++) nie mają takiego powiązania. Po prostu używasz zakresu i kontroli dostępności zapewnianej przez klasę.

AProgrammer
źródło
3
W rzeczywistości Java ma oba z nich.
Joachim Sauer,
3

Unikam klas wewnętrznych w Javie, ponieważ wymiana na gorąco nie działa w obecności klas wewnętrznych. Nienawidzę tego, ponieważ naprawdę nie lubię narażać się na kompromis w kodzie dla takich rozważań, ale te ponowne uruchomienie Tomcat sumują się.

Kevin Cline
źródło
czy ten problem jest gdzieś udokumentowany?
komara
Downvoter: czy moje twierdzenie jest nieprawidłowe?
kevin cline
1
Głosowałem za tym, ponieważ w twojej odpowiedzi brakuje szczegółów, a nie dlatego, że jest niepoprawna. Brak szczegółów utrudnia weryfikację twojego twierdzenia. Jeśli dodasz więcej na ten temat, prawdopodobnie wrócę do opinii, ponieważ twoje informacje wyglądają dość interesująco i mogą być przydatne
gnat
1
@gnat: Przeprowadziłem trochę badań i chociaż wydaje się to być dobrze znaną prawdą, nie znalazłem definitywnego odniesienia do ograniczeń Java HotSwap.
kevin cline
nie jesteśmy na Skeptics.SE :) wystarczyłoby jakieś odniesienie, to nie musi być ostateczne
gnat
2

Jednym z zastosowań prywatnej statycznej klasy wewnętrznej jest wzorzec Memo. Wszystkie dane, które należy zapamiętać, umieścisz w prywatnej klasie i zwrócisz je z jakiejś funkcji jako Object. Nikt na zewnątrz nie może (bez refleksji, deserializacji, kontroli pamięci ...) zaglądać do niego / modyfikować go, ale może zwrócić go twojej klasie i może przywrócić jej stan.

użytkownik470365
źródło
2

Jednym z powodów korzystania z prywatnej klasy wewnętrznej może być to, że używany interfejs API wymaga dziedziczenia od określonej klasy, ale nie chcesz eksportować wiedzy o tej klasie do użytkowników z zewnętrznej klasy. Na przykład:

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

W tym przypadku do_my_async_op wymaga przekazania do niego obiektu typu callback. To wywołanie zwrotne ma podpis funkcji publicznej członka, którego używa interfejs API.

Kiedy do_op () jest wywoływana z klasy zewnętrznej, używa instancji prywatnej klasy wewnętrznej, która dziedziczy z wymaganej klasy wywołania zwrotnego zamiast wskaźnika do siebie. Klasa zewnętrzna ma odwołanie do klasy zewnętrznej w prostym celu przetaczania wywołania zwrotnego do prywatnej funkcji członka do_callback klasy zewnętrznej .

Zaletą tego jest to, że masz pewność, że nikt inny nie może wywołać publicznej funkcji członka „fire ()”.

Kaz Dragon
źródło
2

Według Jona Skeeta w C # w Depth, użycie zagnieżdżonej klasy było jedynym sposobem na wdrożenie w pełni leniwego, bezpiecznego dla wątków singletonu (patrz piąta wersja) (do .NET 4).

Scott Whitlock
źródło
1
jest to idiom inicjalizacji na żądanie , w Javie również wymaga prywatnej statycznej klasy wewnętrznej. Jest zalecany w JMM FAQ , przez Briana Goetza w JCiP 16.4 i przez Joshua Blocha w JavaOne 2008 Bardziej skuteczna Java (pdf) „Dla wysokiej wydajności na polu statycznym ...”
gnat
1

Klasy prywatne i wewnętrzne służą do zwiększania poziomów enkapsulacji i ukrywania szczegółów implementacji.

W C ++, oprócz klasy prywatnej, tę samą koncepcję można osiągnąć, implementując anonimową przestrzeń nazw klasy cpp. Służy to do ukrycia / sprywatyzowania szczegółów implementacji.

Jest to ten sam pomysł, co klasa wewnętrzna lub prywatna, ale na jeszcze większym poziomie enkapsulacji, ponieważ jest całkowicie niewidoczny poza jednostką kompilacji pliku. Nic z tego nie pojawia się w pliku nagłówkowym ani nie jest widoczne na zewnątrz w deklaracji klasy.

hiwaylon
źródło
Wszelkie konstruktywne opinie na temat -1?
hiwaylon
1
Nie przegłosowałem, ale zgaduję, że tak naprawdę nie odpowiadasz na pytanie: nie podajesz żadnego powodu, aby używać klasy prywatnej / wewnętrznej. Właśnie opisujesz, jak to działa i jak zrobić podobną rzecz w c ++
Simon Bergot,
W rzeczy samej. Myślałem, że to było oczywiste przeze mnie i inne odpowiedzi (ukrywanie szczegółów implementacji, zwiększone poziomy enkapsulacji itp.), Ale postaram się wyjaśnić niektóre. Dzięki @Simon.
hiwaylon
1
Niezła edycja. I proszę, nie obrażaj się z powodu tego rodzaju reakcji. Sieć SO stara się egzekwować zasady mające na celu poprawę stosunku sygnału do szumu. Niektórzy ludzie dość surowo podchodzą do zakresu pytań / odpowiedzi i czasami zapominają być mili (komentując na przykład swoje opinie)
Simon Bergot
@Simon Thanks. Jest rozczarowujący z powodu niskiej jakości komunikacji, którą wykazuje. Może zbyt łatwo jest być „surowym” za anonimową zasłoną sieci? No cóż, chyba nic nowego. Jeszcze raz dziękuję za pozytywne opinie.
hiwaylon