Dlaczego prywatne pola nie są wystarczająco chronione?

265

Czy widoczność privatepól / właściwości / atrybutów klasy jest przydatna? W OOP prędzej czy później zamierzasz utworzyć podklasę klasy, w takim przypadku dobrze jest zrozumieć i móc całkowicie zmodyfikować implementację.

Jedną z pierwszych rzeczy, które robię, gdy podklasuję klasę, jest zmiana zestawu privatemetod na protected. Jednak ukrywanie szczegółów przed światem zewnętrznym jest ważne - więc potrzebujemy, protecteda nie tylko public.

Moje pytanie brzmi: czy wiesz o ważnym przypadku użycia, w którym privatezamiast protectedjest dobrym narzędziem, czy dwie opcje „ protected& public” byłyby wystarczające dla języków OOP?

Adam Libuša
źródło
236
Do downvoters: Chociaż zdecydowanie nie zgadzam się z przesłankami PO, głosuję za tym pytaniem, ponieważ jest ono całkowicie spójne i warte odpowiedzi. Tak, PO należy wyjaśnić, dlaczego jest to źle, ale sposobem na to jest napisanie odpowiedzi (lub zasugerowanie zmian w istniejących odpowiedziach), a nie głosowanie tylko dlatego, że jeszcze tego nie wymyślił.
Ixrec,
18
Klasy pochodne są częścią świata zewnętrznego.
CodesInChaos,
20
Nie zapominaj, że ochrona nie zawsze oznacza, że ​​dostęp jest zablokowany w hierarchii dziedziczenia. W Javie zapewnia również dostęp na poziomie pakietu.
berry120
8
Mój profesor zwykł mawiać: „Są rzeczy, których nie powiedziałbym moim dzieciom. To moje prywatne pola”.
SáT

Odpowiedzi:

225

Ponieważ, jak mówisz, protectedwciąż masz możliwość „całkowicie zmodyfikować implementację”. To naprawdę nie chroni niczego w klasie.

Dlaczego zależy nam na „prawdziwej ochronie” rzeczy w klasie? W przeciwnym razie zmiana szczegółów implementacji bez zerwania kodu klienta byłaby niemożliwa . Innymi słowy, ludzie, którzy piszą podklasy, są także „światem zewnętrznym” dla osoby, która napisała oryginalną klasę podstawową.

W praktyce protectedczłonkowie są zasadniczo „publicznym interfejsem API dla podklas” klasy i muszą być stabilni i kompatybilni wstecz tak samo jak publicczłonkowie. Gdybyśmy nie mieli możliwości tworzenia prawdziwych privateczłonków, nic w implementacji nigdy nie byłoby bezpieczne do zmiany, ponieważ nie byłbyś w stanie wykluczyć możliwości, że (nie-złośliwy) kod klienta był w jakiś sposób zależny od to.

Nawiasem mówiąc, podczas gdy „W OOP wcześniej czy później zamierzasz utworzyć podklasę klasy” jest technicznie prawdą, twój argument wydaje się opierać na znacznie silniejszym założeniu, że „wcześniej czy później utworzysz podklasę każda klasa ”, co prawie na pewno nie jest prawdą.

Ixrec
źródło
11
Rzuciłbym cię więcej niż +1, gdybym mógł, ponieważ to pierwszy raz, kiedy miałam privatesens. Perspektywa lib musi być używana częściej, w przeciwnym razie każdy, kto lubi mentalność kodowania „absolutnej kontroli, absolutnej odpowiedzialności”, może ją oznaczyć jako „chroni mnie przed sobą”. Zauważ, że nadal używałem privatepoprzednio, zawsze czułem dobrą dokumentację, a konwencja nazewnictwa, _fooktóra wskazuje, że prawdopodobnie nie powinnaś się z tym bawić, była równoważna, jeśli nie lepsza. Zdolność do deterministycznego powiedzenia „nic się nie złamie” jest uzasadnioną, private-tylko cechą.
abluejelly,
Pierwotnie zignorowałem przypadek kodu bibliotek publicznych i frameworków i myślałem mniej więcej tylko w kategoriach „kodu klienta”. Optymalizacja wewnętrznej implementacji jest dobrym przykładem na moje pytanie, choć zadaję sobie pytanie, czy tak naprawdę dzieje się w rzeczywistości (szczególnie, gdy wiele osób zaleca klasę nie dłuższą niż 1000 linii kodu). Generalnie podoba mi się podejście Ruby, gdzie prywatność jest rodzajem zalecenia: „Oto smoki, postępuj ostrożnie”.
Adam Libuša,
9
@ AdamLibuša Chociaż jest to znacznie większa oferta, jeśli Twój kod jest publiczny, nadal obowiązuje, nawet jeśli jesteś autorem wszystkich klientów klasy. Problem po prostu zmienia się z niektórych reaktorów niemożliwych w niektóre nudne i podatne na błędy. Optymalizacja jest właściwie najmniej powszechnym powodem tych refaktorów z mojego doświadczenia (chociaż głównie robię Javascript), zwykle bardziej przypomina błąd, który ujawnił podstawową wadę implementacji, która wymaga restrukturyzacji wykresu zależności / wywołania różnych bitów wewnętrznych w celu osiągnięcia naprawdę solidne rozwiązanie.
Ixrec,
5
„” prędzej czy później zamierzasz stworzyć podklasę każdej klasy ”, prawie na pewno nie jest tak.” Co więcej, prawie na pewno nie zrobisz tego, a na pewno NIE POWINIENEŚ zastąpić każdej funkcji w klasie i zmienić użycie każdego elementu danych w klasie. Niektóre rzeczy powinny być logicznie zapisane w kamieniu, aby klasa miała jakiekolwiek znaczenie.
Jay
1
Rzuciłbym cię więcej niż +1, gdybym mógł => To właśnie nazywamy bounty @abluejelly
Thomas Ayoub
256

W OOP prędzej czy później zamierzasz utworzyć podklasę klasy

To jest źle. Nie każda klasa powinna być podklasowana, a niektóre statyczne języki OOP mają nawet funkcje, aby temu zapobiec, np. final(Java i C ++) lub sealed(C #).

dobrze jest zrozumieć i móc całkowicie modyfikować implementację.

Nie, nie jest. Dobrze jest, aby klasa była w stanie jasno zdefiniować swój interfejs publiczny i zachować niezmienniki, nawet jeśli są dziedziczone.

Ogólnie kontrola dostępu dotyczy podziału na przedziały. Chcesz, aby poszczególne części kodu były rozumiane bez konieczności szczegółowego zrozumienia, w jaki sposób współdziała z resztą kodu. Prywatny dostęp na to pozwala. Jeśli wszystko jest co najmniej chronione, musisz zrozumieć, co robi każda podklasa, aby zrozumieć, jak działa klasa podstawowa.

Lub, ujmując to w kategoriach Scott Meyers: na prywatne części klasy wpływa skończona ilość kodu: kod samej klasy.

Na części publiczne potencjalnie wpływa każdy istniejący fragment kodu i każdy fragment kodu, który należy jeszcze napisać, co stanowi nieskończoną ilość kodu.

Na części chronione potencjalnie ma wpływ każda istniejąca podklasa i każda podklasa, która ma jeszcze zostać napisana, co jest również nieskończoną ilością kodu.

Wniosek jest taki, że chroniony daje niewiele więcej niż publiczny, podczas gdy prywatny daje prawdziwą poprawę. Istnienie specyfikatora chronionego dostępu jest wątpliwe, a nie prywatne.

Sebastian Redl
źródło
34
+1 za teoretyczny scenariusz „dotknięty nieskończoną ilością kodu”
Broken_Window
13
Być może warto również zauważyć, że projektanci C # faktycznie postanowili zakazać zastępowania odziedziczonej metody, chyba że jest to wyraźnie oznaczone jako virtual, z powodów przedstawionych w tej odpowiedzi.
Will Vousden,
11
Innymi słowy: protecteddotyczy metod, a nie członków danych.
Matthieu M.
7
Nie mogę go teraz znaleźć, ale pamiętam przeczytanie dobrze napisanej odpowiedzi z @EricLippert na temat tego, dlaczego MS sealedduże części biblioteki .net i IIRC, dlaczego chciałby zamknąć resztę. W momencie, gdy pozwolisz spadkobiercom zewnętrznym zacząć dotykać wartości wewnętrznych, musisz dodać ogromną ilość sprawdzeń poprawności / poprawności do każdej metody, ponieważ nie możesz już ufać żadnym niezmiennikom projektu dotyczącym stanu wewnętrznego obiektów.
Dan Neely,
4
@ ThorbjørnRavnAndersen „w trakcie opracowywania, ale nie w momencie wypuszczenia” - skąd u licha ta klasa powinna wiedzieć? Czy naprawdę chcesz skompilować wersję testową inną niż wersja wydana, gdy nie możesz nawet przetestować wersji? Testy i tak nie powinny wymagać dostępu do rzeczy prywatnych.
Sebastian Redl
33

Tak, prywatne pola są absolutnie konieczne. Właśnie w tym tygodniu musiałem napisać niestandardową implementację słownika, w której kontrolowałem zawartość słownika. Gdyby pole słownika miało być chronione lub upublicznione, wówczas kontrole, które tak starannie napisałem, można by łatwo obejść.

Prywatne pola zazwyczaj dotyczą zapewnienia, że ​​dane są zgodne z oczekiwaniami pierwotnego kodera. Uczyń wszystko chronionym / publicznym, a będziesz jeździł autokarem i koniami przez te procedury i walidację.

Robbie Dee
źródło
2
+1 za „jazdę autokarem i koniami” przez cokolwiek. To wspaniałe zdanie, które chciałbym usłyszeć więcej.
Nic Hartley,
2
Jeśli ktoś chce podklasować twoją klasę, być może powinien mieć dostęp do twoich zabezpieczeń? A jeśli nie chcesz, aby ktoś zmienił twoje zabezpieczenia, aby osiągnąć jakiś cel, być może nie udostępniasz kodu? Tak działa w Ruby - private jest mniej więcej zaleceniem.
Adam Libuša,
3
@ AdamLibuša „Nie udostępniaj kodu”? Jak tylko opublikujesz dowolną bibliotekę DLL, udostępniasz kod - wszystkie struktury i metody i wszystko jest dostępne dla całego świata, szczególnie w językach domyślnie obsługujących odbicie. Każdy może robić, co chce, używając „twojego kodu” - privateto tylko sposób na powiedzenie „nie dotykaj ich” i wymuszenie go w ramach umowy kompilatora. W systemach takich jak .NET ma to również istotny wpływ na bezpieczeństwo - możesz dotykać prywatnych użytkowników tylko wtedy, gdy masz pełne zaufanie (w zasadzie równowartość dostępu administratora / roota).
Luaan
2
@ AdamLibuša Myślę, że twoje zamieszanie wynika głównie z różnych podejść do OOP w różnych językach. Podstawą OOP (zgodnie z pierwotną definicją) jest przesyłanie wiadomości - co oznacza, że wszystko jest prywatne, z wyjątkiem wiadomości, na które odpowiadasz. W większości języków OOPish jest to ujawniane jako „zachowaj prywatność swoich danych” - sposób, aby interfejs publiczny był jak najmniejszy. Jedynym sposobem, w jaki użytkownik (podklasa lub inna klasa) musi manipulować klasą, jest zdefiniowany przez ciebie interfejs publiczny - podobnie jak zwykle używasz kierownicy i pedałów do prowadzenia samochodu :)
Luaan
3
@Luaan +1, gdy możesz dotykać szeregowców innych osób!
Nigel Touch
12

Kiedy próbuje rozumować formalnie o poprawności programu Object Oriented to typowy używać modułowego podejścia obejmującego niezmienników obiektów . W tym podejściu

  1. Metody są z nimi związane przed i po (warunki).
  2. Obiekty skojarzyły się z nimi niezmiennikami.

Modułowe wnioskowanie na temat obiektu przebiega w następujący sposób (przynajmniej w pierwszym przybliżeniu)

  1. Wykazać, że konstruktor obiektu ustanawia niezmiennik
  2. Dla każdej nieprywatnej metody załóż, że obiekt niezmienny i warunek wstępny są wstrzymane przy wprowadzaniu, a następnie udowodnij, że treść kodu sugeruje, że warunek końcowy i niezmienny wstrzymują się przy wyjściu z metody

Wyobraź sobie, że weryfikujemy object Aużycie powyższego podejścia. A teraz chcą zweryfikować method gz object Bktórym wzywa method fo object A. Rozumowanie modułowe pozwala nam wnioskować method gbez konieczności ponownego rozważania implementacji method f. Pod warunkiem, że możemy ustalić niezmienność object Ai warunek wstępny method fw witrynie wywołującej method g,, możemy przyjąć warunek postu method fza podsumowanie zachowania wywołania metody. Ponadto dowiemy się również, że po powrocie połączenia niezmiennik Anadal się utrzymuje.

Ta modułowość wnioskowania pozwala nam formalnie myśleć o dużych programach. Możemy uzasadnić każdą z metod z osobna, a następnie zestawić wyniki tego rozumowania z kolei w celu uzasadnienia większych części programu.

Pola prywatne są bardzo przydatne w tym procesie. Aby wiedzieć, że niezmiennik obiektu nadal utrzymuje się między dwoma wywołaniami metody tego obiektu, zwykle polegamy na tym, że obiekt nie jest modyfikowany w okresie pośrednim.

Aby modułowe rozumowanie działało w kontekście, w którym obiekty nie mają pól prywatnych, musielibyśmy mieć sposób, aby upewnić się, że cokolwiek pole zostanie ustawione przez inny obiekt, że niezmiennik zawsze zostanie ponownie ustanowiony (po polu zestaw). Trudno wyobrazić sobie niezmiennik obiektu, który ma zarówno wartość bez względu na wartość pól obiektu, jak i jest użyteczny w uzasadnieniu poprawności programu. Prawdopodobnie musielibyśmy wymyślić jakąś skomplikowaną konwencję dotyczącą dostępu do pola. I prawdopodobnie także tracimy część (w najgorszym nawet całość) naszej zdolności do wnioskowania modułowego.

Pola chronione

Pola chronione przywracają część naszej zdolności rozumowania modułowego. W zależności od języka protectedmoże ograniczać możliwość ustawienia pola na wszystkie podklasy lub wszystkie podklasy i klasy tego samego pakietu. Często zdarza się, że nie mamy dostępu do wszystkich podklas, gdy zastanawiamy się nad poprawnością pisanego obiektu. Na przykład możesz pisać komponent lub bibliotekę, która będzie później używana w większym programie (lub kilku większych programach) - z których niektóre mogą nawet nie zostać jeszcze napisane. Zazwyczaj nie wiesz, czy i w jaki sposób można go zaklasyfikować do podklasy.

Zazwyczaj jednak do podklasy należy utrzymanie obiektu niezmiennego dla klasy, którą rozszerza. Tak więc w języku, w którym ochrona oznacza tylko „podklasę”, a my jesteśmy zdyscyplinowani, aby zapewnić, że podklasy zawsze zachowują niezmienniki swojej nadklasy, można argumentować, że wybór użycia chronionego zamiast prywatnego traci tylko minimalną modułowość .

Chociaż mówiłem o formalnym uzasadnieniu, często uważa się, że gdy programiści nieformalnie rozumują poprawność swojego kodu, czasami opierają się na podobnych typach argumentów.

Flamingpenguin
źródło
8

privatezmienne w klasie są lepsze niż protectedz tego samego powodu, dla którego breakinstrukcja wewnątrz switchbloku jest lepsza niż goto labelinstrukcja; to znaczy, że programiści są podatni na błędy.

protectedzmienne nadają się do nieumyślnego nadużycia (błędy programisty), podobnie jak gotooświadczenie nadaje się do tworzenia kodu spaghetti.

Czy można napisać działający kod bezbłędny przy użyciu protectedzmiennych klas? Tak oczywiście! Tak jak jest to możliwe, aby napisać działający kod wolny od błędów przy użyciu goto; ale jak mówi frazes „Tylko dlatego, że możesz, nie znaczy, że powinieneś!”

Klasy, a nawet paradygmat OO, istnieją, aby uchronić się przed nieszczęśliwymi, podatnymi na błędy programistami ludzkimi, popełniającymi błędy. Obrona przed ludzkimi błędami jest tak dobra, jak środki obronne wbudowane w klasę. Wykonanie implementacji twojej klasy protectedjest równoznaczne z wybiciem ogromnej dziury w ścianach fortecy.

Klasy podstawowe nie mają absolutnie żadnej wiedzy o klasach pochodnych. Jeśli chodzi o klasę podstawową, protectedto nie daje ona żadnej dodatkowej ochrony public, ponieważ nic nie stoi na przeszkodzie, aby klasa pochodna utworzyła publicmoduł pobierający / ustawiający, który zachowuje się jak backdoor.

Jeśli klasa podstawowa pozwala na nieograniczony dostęp do wewnętrznych szczegółów implementacyjnych, wówczas sama klasa nie może się bronić przed błędami. Klasy podstawowe nie mają absolutnie żadnej wiedzy o swoich klasach pochodnych, a zatem nie mają możliwości obrony przed błędami popełnionymi w tych klasach pochodnych.

Najlepszą rzeczą, jaką może zrobić klasa podstawowa, jest ukrycie jak największej jej implementacji privatei wprowadzenie wystarczających ograniczeń, aby uchronić się przed przełamywaniem zmian w klasach pochodnych lub czymkolwiek innym poza klasą.

Ostatecznie istnieją języki wysokiego poziomu, aby zminimalizować błędy ludzkie. Istnieją również dobre praktyki programowania (takie jak zasady SOLID ) w celu zminimalizowania błędów ludzkich.

Deweloperzy oprogramowania, którzy ignorują dobre praktyki programistyczne, mają znacznie większą szansę na niepowodzenie i są bardziej podatni na tworzenie zepsutych, niemożliwych do utrzymania rozwiązań. Ci, którzy postępują zgodnie z dobrymi praktykami, mają znacznie mniejsze szanse na porażkę i są bardziej skłonni do wypracowania działających, możliwych do utrzymania rozwiązań.

Ben Cottrell
źródło
Do downvoter - co można zmienić / ulepszyć w tej odpowiedzi?
Ben Cottrell,
Nie oddałem głosu, ale porównując poziom dostępu z break / goto?
imel96
@ imel96 Nie, porównując powód, dla którego są one omijane i zniechęcają je „Najlepsze praktyki” (szczególnie przy pisaniu nowego kodu). tzn. kompetentny programista unikałby publicszczegółów implementacji, ponieważ nadaje się do kodu niemożliwego do utrzymania. Kompetentny programista unikałby, gotoponieważ nadaje się do kodu niemożliwego do utrzymania. Jednak rzeczywisty świat jest taki, że czasami gubisz się w strasznym bałaganie starszego kodu i nie masz wyboru, jak go użyć goto, i z tego samego powodu czasami nie masz wyboru, jak tylko użyć publicznych / chronionych szczegółów implementacji.
Ben Cottrell
Nasilenie jest różne pod względem wielkości, jest nieporównywalne. Mógłbym żyć z kodem, który ma tylko publicwłaściwości / interfejs, ale nie z kodem, który tylko używa goto.
imel96
2
@ imel96 Czy kiedykolwiek pracowałeś w kodzie, który używa, gotoczy tylko dlatego, że czytasz takie artykuły? Rozumiem, dlaczego goto jest złe, ponieważ spędziłem 2 lata pracując nad starożytnym kodem, który go używa; i spędziłem jeszcze więcej czasu pracując w kodzie, w którym „klasom” wyciekają wszędzie szczegóły implementacji, publica friendspecyfikatory używane są jako szybkie / łatwe / brudne włamania. Nie zgadzam się z argumentem, że „publiczne ujawnianie szczegółów implementacji” nie może powodować bałaganu spaghetti ze sobą na tym samym poziomie dotkliwości, gotoponieważ absolutnie może.
Ben Cottrell
4

Klasy dziedziczone mają dwie umowy - jedną z właścicielami odwołań do obiektów i klas pochodnych. Członkowie publiczni są związani umową z posiadaczami referencji, a członkowie chronieni są związani umową z klasami pochodnymi.

Tworzenie członków protectedsprawia, że ​​jest to bardziej uniwersalna klasa podstawowa, ale często ogranicza możliwości zmiany przyszłych wersji tej klasy. Tworzenie członków privatepozwala autorowi klasy na większą wszechstronność w zmienianiu wewnętrznego funkcjonowania klasy, ale ogranicza rodzaje klas, które można z niej pożytecznie wyprowadzić.

Na przykład List<T>w .NET czyni prywatny magazyn kopii zapasowych; gdyby był chroniony, typy pochodne mogłyby robić użyteczne rzeczy, które w innym przypadku nie List<T>byłyby możliwe, ale przyszłe wersje musiałyby na zawsze korzystać z niezgrabnego monolitycznego sklepu z podkładami, nawet w przypadku list zawierających miliony przedmiotów. Ustanowienie prywatnego magazynu kopii zapasowych pozwoliłoby przyszłym wersjom List<T>na korzystanie z bardziej wydajnego magazynu kopii zapasowych bez rozbijania klas pochodnych.

supercat
źródło
4

Myślę, że w twoim argumencie jest kluczowe założenie, że kiedy ktoś pisze klasę, nie wie, kto może przedłużyć tę klasę i z jakiego powodu . Biorąc pod uwagę to założenie, twój argument miałby doskonały sens, ponieważ każda zmienna, którą wybierzesz jako prywatną, mogłaby potencjalnie odciąć drogę rozwoju. Odrzuciłbym jednak to założenie.

Jeśli to założenie zostanie odrzucone, należy rozważyć tylko dwa przypadki.

  1. Autor oryginalnej klasy miał bardzo jasne pomysły na to, dlaczego można ją rozszerzyć (np. Jest to BaseFoo, a później będzie kilka konkretnych implementacji Foo).

W tym przypadku autor wie, że ktoś rozszerzy klasę i dlaczego, a zatem będzie wiedział dokładnie, co chronić, a co prywatnie. Używają rozróżnienia prywatnego / chronionego, aby komunikować się z pewnym rodzajem interfejsu dla użytkownika tworzącego podklasę.

  1. Autor klasy podrzędnej próbuje włamać się w pewien sposób do klasy nadrzędnej.

Ten przypadek powinien być rzadki (można argumentować, że nie jest uzasadniony) i nie jest preferowany po prostu modyfikowanie oryginalnej klasy w oryginalnej bazie kodu. Może to być również objaw złego projektu. W takich przypadkach wolałbym, aby osoba hakująca w tym zachowaniu korzystała z innych hacków, takich jak przyjaciele (C / C ++) i setAccessible(true)(Java).

Myślę, że można bezpiecznie odrzucić to założenie.

Zasadniczo sprowadza się to do koncepcji kompozycji zamiast dziedziczenia . Dziedziczenie jest często nauczane jako idealny sposób na ograniczenie ponownego użycia kodu, jednak rzadko powinien być pierwszym wyborem do ponownego użycia kodu. Nie mam prostego argumentu powalającego, a jego zrozumienie może być dość trudne i sporne. Jednak w moim doświadczeniu z modelowaniem domen odkryłem, że rzadko korzystam z dziedziczenia bez dokładnego zrozumienia, kto i dlaczego odziedziczy moją klasę.

Tempo
źródło
2

Wszystkie trzy poziomy dostępu mają przypadek użycia, brak OOP byłby niekompletny. Zwykle tak

  • uczyń wszystkie zmienne / członków danych prywatnymi . Nie chcesz, aby ktoś z zewnątrz bałaganił twoich danych wewnętrznych. Również metody zapewniające funkcje pomocnicze (obliczenia oparte na kilku zmiennych składowych) w publicznym lub chronionym interfejsie - jest to wyłącznie do użytku wewnętrznego i możesz chcieć go zmienić / ulepszyć w przyszłości.
  • upublicznij ogólny interfejs swojej klasy . Właśnie z tym powinni pracować użytkownicy twojej oryginalnej klasy i jak powinny wyglądać klasy pochodne. W celu zapewnienia właściwego enkapsulacji są to zwykle tylko metody (i klasy pomocnicze / struktury, wyliczenia, typy typef, cokolwiek użytkownik potrzebuje do pracy z twoimi metodami), a nie zmienne.
  • zadeklaruj metody chronione, które mogą być przydatne dla kogoś, kto chce rozszerzyć / specjalizować funkcjonalność twojej klasy, ale nie powinien być częścią publicznego interfejsu - w rzeczywistości zwykle podnosisz prywatnych członków do ochrony, gdy jest to konieczne. Jeśli masz wątpliwości, nie wiesz, dopóki się tego nie dowiesz
    1. twoja klasa może / może / zostanie sklasyfikowana,
    2. i mieć jasny pomysł, jakie mogą być przypadki użycia podklas.

Odchodzisz od tego ogólnego schematu tylko wtedy, gdy istnieje ku temu dobry powód ™ . Uważaj na „to ułatwi moje życie, kiedy będę mieć swobodny dostęp do niego z zewnątrz” (a tutaj na zewnątrz także podklasy). Kiedy wdrażam hierarchie klas, często zaczynam od klas, które nie mają chronionych członków, dopóki nie przechodzę do ich podklasowania / rozszerzania / specjalizacji, stając się podstawowymi klasami frameworka / zestawu narzędzi i czasami przenosząc część ich oryginalnej funkcjonalności o jeden poziom wyżej.

Murphy
źródło
1

Być może bardziej interesujące pytanie dotyczy tego, dlaczego potrzebny jest jakikolwiek inny rodzaj pola niż prywatny. Gdy podklasa musi wchodzić w interakcję z danymi nadklasy, czynność ta bezpośrednio tworzy bezpośrednie połączenie między nimi, podczas gdy stosowanie metod zapewniających interakcję między nimi pozwala na poziom pośredni, który umożliwia wprowadzenie zmian w nadklasa, która w innym przypadku byłaby bardzo trudna.

Wiele języków (np. Ruby i Smalltalk) nie udostępnia pól publicznych, dlatego programiści nie mogą zezwalać na bezpośrednie łączenie z implementacjami swoich klas, ale dlaczego nie pójść dalej i mieć tylko pola prywatne? Nie nastąpiłaby utrata ogólności (ponieważ nadklasa zawsze może zapewniać chronione akcesoria dla podklasy), ale zapewniłaby, że klasy zawsze miałyby co najmniej niewielki stopień izolacji od swoich podklas. Dlaczego nie jest to bardziej powszechny projekt?

Jules
źródło
1

Wiele dobrych odpowiedzi tutaj, ale i tak wrzucę dwa centy. :-)

Prywatne jest dobre z tego samego powodu, dla którego dane globalne są złe.

Jeśli klasa deklaruje dane jako prywatne, to absolutnie wiesz, że jedynym kodem, który ma problemy z tymi danymi, jest kod w klasie. Kiedy pojawia się błąd, nie musisz przeszukiwać całego stworzenia, aby znaleźć każde miejsce, które może zmienić te dane. Wiesz, że jest w klasie. Po wprowadzeniu zmiany w kodzie i zmianie sposobu korzystania z tego pola nie trzeba śledzić wszystkich potencjalnych miejsc, które mogłyby korzystać z tego pola, i badać, czy planowana zmiana je złamie. Wiesz, że jedyne miejsca są w klasie.

Miałem wiele, wiele razy, że musiałem wprowadzać zmiany do klas, które są w bibliotece i są używane przez wiele aplikacji, i muszę bardzo ostrożnie stąpać, aby upewnić się, że nie zepsuję aplikacji, o których nic nie wiem. Im więcej danych publicznych i chronionych, tym większy potencjał problemów.

Sójka
źródło
1

Myślę, że warto wspomnieć o kilku odmiennych opiniach.

Teoretycznie dobrze jest mieć kontrolowany poziom dostępu ze wszystkich powodów wymienionych w innych odpowiedziach.

W praktyce zbyt często podczas przeglądania kodu widzę osoby (lubiące korzystać z prywatnych), zmieniające poziom dostępu z prywatnego -> chroniony i niezbyt często z chronionego -> publiczny. Prawie zawsze zmiana właściwości klasy wiąże się z modyfikacją ustawiaczy / getterów. To zmarnowało wiele mojego czasu (przegląd kodu) i ich (zmiana kodu).

Denerwuje mnie również to, że ich klasy nie są zamknięte do modyfikacji.

To było z wewnętrznym kodem, w którym zawsze możesz go zmienić, jeśli potrzebujesz. Gorzej jest z kodem innej firmy, gdy zmiana kodu nie jest taka łatwa.

Ilu programistów uważa, że ​​to kłopot? Cóż, ilu używa języków programowania, które nie mają prywatnego? Oczywiście ludzie nie tylko używają tych języków, ponieważ nie mają prywatnych specyfikatorów, ale pomaga uprościć języki, a prostota jest ważna.

Imo jest bardzo podobny do pisania dynamicznego / statycznego. Teoretycznie pisanie statyczne jest bardzo dobre. W praktyce uniemożliwia to tylko jak 2% błędów nadmierną Efektywność Dynamiczny piszą ... . Korzystanie z trybu prywatnego prawdopodobnie zapobiega mniejszym błędom.

Uważam, że zasady SOLID są dobre. Chciałbym, żeby ludzie dbali o nie bardziej, niż dbali o stworzenie klasy publicznej, chronionej i prywatnej.

imel96
źródło
1
Jeśli musisz zmienić kod innej firmy podczas korzystania z niego, albo nie jest on dobrze zaprojektowany, albo nie używasz go dobrze. Zawsze możesz ponownie wykorzystać klasę nieabstrakcyjną, kapsułkując ją, ale w praktyce prawie nigdy nie trzeba jej podklasować.
Little Santi
0

Chciałbym również dodać kolejny praktyczny przykład, dlaczego protectedto nie wystarczy. Na mojej uczelni pierwsze lata podejmują projekt, w którym muszą opracować wersję komputerową gry planszowej (do której później opracowano sztuczną inteligencję i jest ona połączona z innymi graczami przez sieć). Dostarczono im częściowy kod umożliwiający rozpoczęcie pracy, w tym środowisko testowe. Niektóre właściwości głównej klasy gry są ujawnione w protectedtaki sposób, że klasy testowe, które rozszerzają tę klasę, mają do nich dostęp. Ale te pola nie są poufnymi informacjami.

Jako TA dla jednostki często widzę, że uczniowie po prostu tworzą cały swój dodany kod protectedlub public(być może dlatego, że widzieli inne protectedi inne publicrzeczy i zakładali, że powinni pójść za nimi). Pytam ich, dlaczego ich poziom ochrony jest niewłaściwy, a wielu nie wie dlaczego. Odpowiedź jest taka, że ​​wrażliwe informacje, które ujawniają w podklasach, oznaczają, że inny gracz może oszukiwać, po prostu rozszerzając tę ​​klasę i uzyskując dostęp do bardzo wrażliwych informacji w grze (zasadniczo ukrytej lokalizacji przeciwników, myślę, że podobnie jak by to było, gdybyś mogli zobaczyć elementy przeciwników na planszy pancerników, powiększając niektóre klasy). To sprawia, że ​​ich kod jest bardzo niebezpieczny w kontekście gry.

Poza tym istnieje wiele innych powodów, aby zachować prywatność nawet dla swoich podklas. Może to być ukrywanie szczegółów implementacji, które mogłyby zepsuć poprawne działanie klasy, jeśli zostaną zmienione przez kogoś, kto niekoniecznie wie, co robią (głównie myśląc o innych ludziach używających twojego kodu tutaj).

J_mie6
źródło
1
Nie rozumiem. Jeśli gracze nie będą dynamicznie rozszerzać klas podczas gry, jak można to wykorzystać do oszukiwania? Oznaczałoby to, że importujesz niezaufany kod do swojej bazy kodów. Jeśli mówisz, że dajesz uczniom skompilowane zajęcia i zapobiegasz oszustwom w ich zadaniu poprzez modyfikację szczegółów implementacji, ustawienie poziomu ochrony nie spowoduje, że zadanie będzie bardziej bezpieczne. Uczniowie mogą dekompilować biblioteki lub wykorzystać refleksję na swoją korzyść. Zależy to oczywiście od poziomu egzekwowania, którego szukasz.
Sam
@sam Zasadniczo kod powinien być napisany przy założeniu, że ktokolwiek go zmodyfikuje w następnej kolejności, nie będzie musiał znać całej bazy kodu na wylot. Czasami może to być nawet oryginalny autor (upływ czasu, brak snu ...). Oszukiwanie może polegać na tym, że uczniowie otrzymują szkielet kodu i zmieniają logikę, której nie powinni dotykać.
Phil Lello,
0

Prywatne metody / zmienne będą na ogół ukryte przed podklasą. To może być dobra rzecz.

Prywatna metoda może przyjmować założenia dotyczące parametrów i pozostawić sprawdzanie poprawności dzwoniącemu.

Metoda chroniona powinna sprawdzać poprawność danych wejściowych.

Phil Lello
źródło
-1

„prywatny” oznacza: Nie jest przeznaczony do zmiany ani dostępu dla kogokolwiek poza samą klasą. Nieprzeznaczony do zmiany lub dostępu przez podklasy. Podklasy? Jakie podklasy? Nie należy tego podklasować!

„chroniony” oznacza: przeznaczony wyłącznie do zmiany lub dostępu przez klasy lub podklasy. Prawdopodobne wnioskowanie, które należy podklasować, w przeciwnym razie dlaczego „chroniony”, a nie „prywatny”?

Jest tutaj wyraźna różnica. Jeśli zrobię coś prywatnego, powinieneś trzymać od tego brudne palce. Nawet jeśli jesteś podklasą.

gnasher729
źródło
-1

Jedną z pierwszych rzeczy, które robię, gdy podklasuję klasę, jest zmiana szeregu prywatnych metod na chronione

Niektóre rozumowanie o privatevs. protected metod :

privatemetody zapobiegają ponownemu użyciu kodu. Podklasa nie może użyć kodu w metodzie prywatnej i może wymagać ponownego zaimplementowania go - lub ponownej implementacji metod, które pierwotnie zależą od metody prywatnej & c.

Z drugiej strony każdą metodę, która nie private jest postrzegana jako interfejs API dostarczony przez klasę do „świata zewnętrznego”, w tym sensie, że podklasy innych firm również są uważane za „świat zewnętrzny”, jak ktoś inny sugerował w swojej odpowiedzi już.

Czy to źle? - Nie wydaje mi się.

Oczywiście (pseudo-) publiczny interfejs API blokuje pierwotnego programistę i utrudnia refaktoryzację tych interfejsów. Ale patrząc na drugą stronę, dlaczego programista nie powinien projektować własnych „szczegółów implementacji” w sposób tak czysty i stabilny jak jego publiczny interfejs API? Czy powinien to privatezrobić, aby nie był niechętny do konstruowania swojego „prywatnego” kodu? Myślisz, że może to później posprząta, bo nikt tego nie zauważy? - Nie.

Programiści powinni również przemyśleć trochę swój „prywatny” kod, aby ustrukturyzować go w sposób, który pozwala, a nawet promuje ponowne wykorzystanie jego jak największej liczby. Wówczas części nieprywatne mogą nie stać się tak dużym obciążeniem w przyszłości, jak niektórzy się obawiają.

Wiele (ramowej) kodu widzę przyjmuje niespójne stosowanie private: protectedmetody non-końcowe, które ledwo robić nic więcej niż delegowanie do prywatnej metody jest powszechnie spotykane. protected, nie ostateczne metody, których umowa może zostać zrealizowana jedynie poprzez bezpośredni dostęp do pól prywatnych.

Te metody nie mogą być logicznie zastąpione / ulepszone, chociaż technicznie nie ma nic, co by uczyniło to (kompilatorem) oczywistym.

Chcesz możliwości rozszerzenia i dziedziczenia? Nie twórzcie swoich metod private.

Nie chcesz zmienić niektórych zachowań twojej klasy? Twórz swoje metody final.

Czy naprawdę nie można wywołać metody poza określonym, dobrze określonym kontekstem? Udostępnij swoją metodę privatei / lub zastanów się, jak udostępnić wymagany, dobrze zdefiniowany kontekst do ponownego użycia za pomocą innej protectedmetody opakowania.

Dlatego privatezalecam oszczędne używanie . I nie mylić privatez final. - Jeśli implementacja metody jest istotna dla ogólnego kontraktu klasy i dlatego nie może być zastąpiona / zastąpiona, zrób to final!

Dla pól privatenie jest naprawdę źle. Tak długo, jak pole (pola) można rozsądnie „wykorzystać” za pomocą odpowiednich metod (to nie jest getXX() lub setXX()!).

JimmyB
źródło
2
-1 to nie rozwiązuje privatevs protectedoryginalne pytanie, daje błędne informacje ( „używać privateoszczędnie”?) I jest sprzeczne z ogólnym konsensusu w sprawie projektu OO. Korzystanie privatenie ma nic wspólnego z ukrywaniem niechlujnego kodu.
Andres F.,
3
Wspominasz, że klasa ma interfejs API, ale wydaje się, że nie nawiązuje połączenia, że ​​użytkownik interfejsu API nie chce być obciążony szczegółami, których nie musi znać. Idealną sytuacją dla użytkownika klasy jest to, aby interfejs zawierał tylko potrzebne metody i nic więcej. Tworzenie metod privatepomaga utrzymać API w czystości.
Ben Cottrell,
1
@ HannoBinder Zgadzam się, że istnieje wiele klas cierpiących na sztywność, jednak elastyczność i ponowne użycie kodu są znacznie mniej ważne niż hermetyzacja i długoterminowa łatwość konserwacji. Rozważmy przypadek, w którym wszystkie klasy mają chronionych członków, a klasy pochodne mogą zadzierać z dowolnymi szczegółami implementacyjnymi, jakie chcą; Jak w niezawodny sposób testujesz te klasy, wiedząc, że cokolwiek w dowolnej części niepisanego kodu może spowodować awarię tego kodu w dowolnym momencie? ile takich „hacków” mógłby zrobić kod, zanim wszystko przekształci się w wielką kulę błota?
Ben Cottrell,
2
Och, powiedziałeś „członkowie”. To, co mówię, dotyczy tylko metod . Zmienne składowe (pola) to inna historia, w której prywatność jest znacznie bardziej uzasadniona.
JimmyB,
2
Nomenklatura w dyskusji jest wszędzie, ponieważ nie jest specyficzna dla konkretnego języka. „Członek” w C ++ może być danymi, funkcją, typem lub szablonem.
JDługosz
-1

Czy wiesz o ważnym przypadku użycia, w którym prywatne zamiast chronione jest dobrym narzędziem, czy też dwie opcje „chronione i publiczne” wystarczą dla języków OOP?

Prywatne : gdy masz coś, co nigdy nie będzie przydatne dla żadnej podklasy do wywołania lub zastąpienia.

Chroniony : gdy masz coś, co ma implementację / stałą dla podklasy.

Przykład:

public abstract Class MercedesBenz() extends Car {
  //Might be useful for subclasses to know about their customers
  protected Customer customer; 

  /* Each specific model has its own horn. 
     Therefore: protected, so that each subclass might implement it as they wish
  */
  protected abstract void honk();

  /* Taken from the car class. */
  @Override
  public void getTechSupport(){
     showMercedesBenzHQContactDetails(customer);
     automaticallyNotifyLocalDealer(customer);
  }

  /* 
     This isn't specific for any subclass.
     It is also not useful to call this from inside a subclass,
     because local dealers only want to be notified when a 
     customer wants tech support. 
   */
  private void automaticallyNotifyLocalDealer(){
    ...
  }
}
Arnab Datta
źródło
2
kiedy masz coś, co nigdy nie przyda się żadnej podklasie do wywołania lub zastąpienia - I jest to coś, czego moim zdaniem nigdy wcześniej nie wiesz.
Adam Libuša
Cóż, być może będę musiał nacisnąć przycisk resetowania na moim CMOS. Ale domyślnym założeniem jest to, że tak naprawdę nie będę go potrzebować, dlatego jest on umieszczony w podwoziu. To samo dotyczy metod. Zapewniasz mu ochronę, jeśli podklasa absolutnie musi ją zaimplementować / wywołać (patrz: honking). Podajesz go do wiadomości publicznej, jeśli inne osoby muszą do niego zadzwonić.
Arnab Datta
-2

Trudno mi było zrozumieć tę sprawę, dlatego chciałbym podzielić się częścią mojego doświadczenia:

  • Jakie jest chronione pole? To nic więcej niż pola, które nie mogą być dostępne poza klasą, czyli publicznie tak: $classInstance->field. I sztuczka polegająca na tym, że „to jest to”. Dzieci twojej klasy będą miały do ​​niej pełny dostęp, ponieważ jest to ich prawowita część wewnętrzna.
  • Co to jest pole prywatne ? To „ prawdziwa prywatność ” dla twojej własnej klasy i twojej własnej implementacji tej klasy. „Przechowywać w miejscu niedostępnym dla dzieci”, tak jak na butelce z lekiem. Będziesz mieć gwarancję, że nie da się go zastąpić pochodnymi twojej klasy, twoje metody - po wywołaniu - będą miały dokładnie to, co zadeklarowałeś

AKTUALIZACJA: praktyczny przykład prawdziwego zadania, które rozwiązałem. Oto on: masz token, taki jak USB lub LPT (tak było w moim przypadku) i masz oprogramowanie pośrednie. Token prosi o podanie kodu PIN, otwiera się, jeśli jest poprawny, i możesz wysłać zaszyfrowaną część i liczbę kluczy do rozszyfrowania. Klucze są przechowywane w tokenie, nie można ich odczytać, należy ich używać. I były tymczasowe klucze do sesji, podpisane kluczem w tokenie, ale przechowywane w samym oprogramowaniu pośrednim. Klawisz tymczasowy nie miał przeciekać gdziekolwiek na zewnątrz, tylko po to, aby istniał na poziomie kierowcy. I użyłem prywatnych pól do przechowywania tego klucza tymczasowego i niektórych danych związanych z połączeniem sprzętowym. Dlatego żadne pochodne nie były w stanie korzystać nie tylko z interfejsu publicznego, ale także z niektórych chronionych„przydatne” podprogramy, które wykonałem dla zadania, ale nie udało mi się otworzyć sejfu za pomocą klawiszy i interakcji HW. Ma sens?

Alexey Vesnin
źródło
Przepraszam chłopaki! właśnie je pomieszałem - aktualizacja mojej odpowiedzi =) DZIĘKI! Spieszyłem się i pomyliłem =)
Alexey Vesnin
2
Myślę, że PO zdaje sobie sprawę z różnic między dwoma poziomami ochrony, ale jest zainteresowany tym, dlaczego jeden z nich powinien być używany ponad drugim.
Sam
@Sam proszę głębiej przeczytać moją odpowiedź. Są to zupełnie różne rodzaje pól! tylko rzecz mają wspólną jest to, że oboje nie można odwoływać się publicznie
Alexey Vesnin
2
Nie jestem pewien, o czym mówisz. Mam świadomość różnic między prywatnym a chronionym. Być może źle zrozumiałeś, co mam na myśli przez poziomy ochrony? Mam na myśli „poziom dostępu”. Próbowałem zwrócić uwagę, że chociaż twoja odpowiedź nie jest zła, nie odnosi się bezpośrednio do pytania. Wygląda na to, że PO wie, co oznaczają zarówno chronione / prywatne / publiczne, ale nie jest pewny, w jakich okolicznościach chcieliby wybrać jedną z nich. W ten sposób możesz zostać odrzucony (nie przeze mnie).
Sam
@Sam Myślę, że mam twój punkt - dodał praktyczny przypadek z mojej prawdziwej praktyki / doświadczenia. Czy tego brakuje w twoim punkcie?
Alexey Vesnin