Czy należy kiedykolwiek używać chronionych zmiennych składowych?

97

Czy należy kiedykolwiek używać chronionych zmiennych składowych? Jakie są zalety i jakie problemy może to powodować?

John Channing
źródło

Odpowiedzi:

74

Czy należy kiedykolwiek używać chronionych zmiennych składowych?

Zależy od tego, jak bardzo jesteś wybredny w kwestii ukrywania się.

  • Jeśli nie chcesz przecieków stanu wewnętrznego, zadeklarowanie wszystkich zmiennych składowych jako prywatnych jest drogą do zrobienia.
  • Jeśli tak naprawdę nie obchodzi cię, że podklasy mogą uzyskać dostęp do stanu wewnętrznego, to chronione jest wystarczająco dobre.

Jeśli pojawi się programista i podklasuje twoją klasę, może to zepsuć, ponieważ nie zrozumie jej w pełni. W przypadku członków prywatnych, innych niż interfejs publiczny, nie mogą zobaczyć szczegółów implementacji dotyczących sposobu wykonywania czynności, co zapewnia elastyczność późniejszej zmiany.

Allain Lalonde
źródło
1
Czy możesz wypowiedzieć się na temat wydajności chronionych zmiennych w porównaniu ze zmienną prywatną metodą get / set?
Jake,
3
Powiedziałbym, że nie jest to coś, czym warto się martwić, chyba że poprzez profilowanie okaże się, że szyjka butelki staje się akcesoriami (czego prawie nigdy nie jest). Istnieją sztuczki, które można zrobić, aby JIT był mądrzejszy, jeśli okaże się to problemem. Na przykład w Javie można wskazać, że akcesor może być wstawiony, oznaczając go jako ostateczny. Chociaż szczerze mówiąc, wydajność metod pobierających i ustawiających jest znacznie mniej ważna niż zajmowanie się organizacją systemu i rzeczywistymi problemami z wydajnością określonymi przez profilera.
Allain Lalonde
23
@Jake: Nigdy nie powinieneś podejmować decyzji projektowych na podstawie założeń dotyczących wydajności. Podejmujesz decyzje projektowe na podstawie tego, co uważasz za najlepszy projekt i tylko wtedy, gdy profilowanie w prawdziwym życiu ujawnia wąskie gardło w projekcie, idź i napraw go. Zwykle, jeśli projekt jest dobry, wydajność również jest dobra.
Mecki
Z członkami prywatnymi, innymi niż publiczny interfejs, nie widzą szczegółów dotyczących implementacji. Mogą po prostu otworzyć klasę i wyszukać ją, więc to nie ma sensu ?!
Czarny
2
@Black Oczywiście Allain miał na myśli „nie mogą uzyskać dostępu ” do tych członków i dlatego nie mogą zbudować kodu przeciwko nim, pozostawiając autorowi klasy możliwość późniejszego usunięcia / zmiany chronionych członków. (Oczywiście idiom pimpl umożliwiłby ukrycie ich wizualnie i przed jednostkami tłumaczeniowymi, w tym nagłówkiem.)
underscore_d
31

Obecnie ogólne odczucie jest takie, że powodują one nadmierne sprzężenie między klasami pochodnymi a ich bazami.

Nie mają szczególnej przewagi nad chronionymi metodami / właściwościami (dawno temu mogły mieć niewielką przewagę wydajnościową), a także były bardziej używane w czasach, gdy modne było bardzo głębokie dziedziczenie, czego obecnie nie ma.

Will Dean
źródło
2
Nie powinno no particular advantage over protected methods/propertiesbyć no particular advantage over *private* methods/properties?
Penghe Geng
Nie, ponieważ mówię / mówiłem o zaletach / wadach różnych sposobów komunikowania się między klasami pochodnymi i ich bazami - wszystkie te techniki byłyby „chronione” - różnica polega na tym, czy są to zmienne składowe (pola), czy właściwości / metody ( tj. jakiegoś rodzaju podprogramy).
Will Dean
1
Dzięki za szybkie wyjaśnienie. Cieszę się, że w ciągu godziny otrzymam odpowiedź oryginalnego nadawcy na moje pytanie do postu sprzed 6 lat. Nie sądzisz, że może się to zdarzyć na większości innych forów internetowych :)
Penghe Geng
9
Jeszcze bardziej niezwykłe jest to, że faktycznie zgadzam się ze sobą przez ten czas ...
Will Dean
Jednym z zadań konstruktora jest upewnienie się, że wszystkie zmienne stanu są jawnie zainicjowane. Jeśli przestrzegasz tej konwencji, możesz użyć superkonstrukcji do wywołania konstruktora nadrzędnego; zajmie się wtedy inicjalizacją prywatnych zmiennych stanu w klasie nadrzędnej.
ncmathsadist
31

Ogólnie rzecz biorąc, jeśli coś nie jest celowo pomyślane jako publiczne, ustawiam to jako prywatne.

Jeśli zajdzie sytuacja, w której potrzebuję dostępu do tej prywatnej zmiennej lub metody z klasy pochodnej, zmieniam ją z prywatnej na chronioną.

To rzadko się zdarza - naprawdę nie jestem fanem dziedziczenia, ponieważ nie jest to szczególnie dobry sposób na modelowanie większości sytuacji. W każdym razie kontynuuj, bez obaw.

Powiedziałbym, że jest to w porządku (i prawdopodobnie najlepszy sposób na zrobienie tego) dla większości programistów.

Prosty fakt jest taki , że jeśli jakiś inny programista pojawi się rok później i zdecyduje, że potrzebuje dostępu do twojej prywatnej zmiennej członkowskiej, po prostu zamierzają edytować kod, zmienić go na chroniony i kontynuować swoją działalność.

Jedynymi prawdziwymi wyjątkami od tej reguły są sytuacje, w których prowadzisz działalność polegającą na wysyłaniu binarnych bibliotek dll w formie czarnej skrzynki do stron trzecich. Składa się w zasadzie z Microsoftu, dostawców „Custom DataGrid Control” i może kilku innych dużych aplikacji, które są dostarczane z bibliotekami rozszerzalności. Jeśli nie należysz do tej kategorii, nie warto tracić czasu / wysiłku, aby martwić się o tego typu rzeczy.

Orion Edwards
źródło
8

Kluczową kwestią dla mnie jest to, że po utworzeniu zmiennej chronionej nie możesz pozwolić żadnej metodzie w Twojej klasie na poleganie na jej wartości w zakresie, ponieważ podklasa zawsze może umieścić ją poza zakresem.

Na przykład, jeśli mam klasę, która definiuje szerokość i wysokość renderowanego obiektu i chronię te zmienne, nie mogę wtedy przyjmować żadnych założeń dotyczących (na przykład) proporcji.

Co najważniejsze, nigdy nie mogę przyjąć tych założeń w żadnym momencie od momentu wydania kodu jako biblioteki, ponieważ nawet jeśli zaktualizuję moje setery, aby zachować proporcje, nie mam gwarancji, że zmienne są ustawiane przez setery lub dostępne za pośrednictwem metody pobierające w istniejącym kodzie.

Żadna podklasa z mojej klasy nie może też zdecydować się na taką gwarancję, ponieważ nie może również wymusić wartości zmiennych, nawet jeśli jest to cały punkt ich podklasy .

Jako przykład:

  • Mam klasę prostokąta z szerokością i wysokością, która jest przechowywana jako chronione zmienne.
  • Oczywistą podklasą (w moim kontekście) jest klasa „DisplayedRectangle”, w której jedyną różnicą jest to, że ograniczam szerokości i wysokości do prawidłowych wartości dla wyświetlania graficznego.
  • Ale teraz jest to niemożliwe , ponieważ moja klasa DisplayedRectangle nie może naprawdę ograniczać tych wartości, ponieważ każda jej podklasa może bezpośrednio przesłonić wartości, będąc nadal traktowaną jako DisplayedRectangle.

Ograniczając zmienne, aby były prywatne, mogę wymusić zachowanie, które chcę, za pomocą metod ustawiających lub pobierających.

deworde
źródło
7

Ogólnie rzecz biorąc, chciałbym zachować chronione zmienne członkowskie w rzadkich przypadkach, w których masz całkowitą kontrolę nad kodem, który również ich używa. Jeśli tworzysz publiczny interfejs API, powiedziałbym, że nigdy. Poniżej będziemy odnosić się do zmiennej składowej jako do „właściwości” obiektu.

Oto, czego twoja nadklasa nie może zrobić po ustawieniu zmiennej składowej chronionej, a nie prywatnej z dostępami:

  1. leniwie twórz wartości w locie, gdy właściwość jest odczytywana. Jeśli dodasz chronioną metodę pobierania, możesz leniwie utworzyć wartość i przekazać ją z powrotem.

  2. wiedzieć, kiedy właściwość została zmodyfikowana lub usunięta. Może to powodować błędy, gdy nadklasa przyjmuje założenia dotyczące stanu tej zmiennej. Utworzenie chronionej metody ustawiającej dla zmiennej zachowuje tę kontrolę.

  3. Ustaw punkt przerwania lub dodaj dane wyjściowe debugowania, gdy zmienna jest odczytywana lub zapisywana.

  4. Zmień nazwę tej zmiennej składowej bez przeszukiwania całego kodu, który może jej używać.

Ogólnie myślę, że byłby to rzadki przypadek, w którym zalecałbym utworzenie chronionej zmiennej składowej. Lepiej poświęcić kilka minut na ujawnianie właściwości za pomocą metod pobierających / ustawiających, niż godzin później na śledzenie błędu w jakimś innym kodzie, który modyfikował chronioną zmienną. Nie tylko to, ale jesteś ubezpieczony przed dodawaniem przyszłych funkcji (takich jak leniwe ładowanie) bez przerywania zależnego kodu. Trudniej to zrobić później niż teraz.

Michael Bishop
źródło
7

Na poziomie projektowania może być właściwe użycie chronionej właściwości, ale w przypadku implementacji nie widzę korzyści w mapowaniu jej na chronioną zmienną składową zamiast metod akcesora / mutatora.

Chronione zmienne członkowskie mają znaczące wady, ponieważ skutecznie umożliwiają kodowi klienta (podklasie) dostęp do wewnętrznego stanu klasy bazowej. Zapobiega to efektywnemu utrzymywaniu niezmienników przez klasę bazową.

Z tego samego powodu chronione zmienne składowe również znacznie utrudniają pisanie bezpiecznego kodu wielowątkowego, chyba że gwarantowana stała lub ograniczona do pojedynczego wątku.

Metody Accessor / mutator oferują znacznie większą stabilność API i elastyczność implementacji w trakcie konserwacji.

Ponadto, jeśli jesteś purystą OO, obiekty współpracują / komunikują się, wysyłając wiadomości, a nie czytając / ustawiając stan.

W zamian oferują bardzo niewiele korzyści. Niekoniecznie bym je usunął z cudzego kodu, ale sam ich nie używam.

richj
źródło
4

W większości przypadków korzystanie z funkcji chronionych jest niebezpieczne, ponieważ w pewnym stopniu zrywasz hermetyzację swojej klasy, co może zostać zepsute przez źle zaprojektowaną klasę pochodną.

Ale mam jeden dobry przykład: powiedzmy, że możesz użyć jakiegoś ogólnego pojemnika. Ma wewnętrzną implementację i wewnętrzne akcesory. Ale musisz zaoferować co najmniej 3 publiczny dostęp do jego danych: mapa, hash_map, vector-like. Wtedy masz coś takiego:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

Użyłem tego rodzaju kodu mniej niż miesiąc temu (więc kod pochodzi z pamięci). Po namyśle uważam, że chociaż ogólny kontener Base powinien być klasą abstrakcyjną, to nawet jeśli może całkiem dobrze żyć, bo używanie bezpośrednio Base byłoby tak uciążliwe, że powinno być zabronione.

Podsumowanie W ten sposób masz chronione dane używane przez klasę pochodną. Musimy jednak wziąć pod uwagę fakt, że klasa Base powinna być abstrakcyjna.

paercebal
źródło
nie przerywa hermetyzacji bardziej niż członkowie publiczni. Jest to ustawienie, które mówi, że klasy pochodne mogą używać stanu klasy, który nie jest ujawniany użytkownikom tej klasy.
gbjbaanb
@gbjbaanb: Zaprzeczasz sobie „to nie psuje hermetyzacji bardziej niż członkowie publiczni” różni się od „[tylko] klasy pochodne mogą używać stanu klasy”. „chroniony” to środek między publicznym a prywatnym. Tak więc „chroniony [...] przerywa nieco hermetyzację” jest nadal
aktualny
w rzeczywistości w języku c ++ adaptery kontenerów, takie jak std :: stack, ujawnią podstawowy obiekt kontenera za pomocą chronionej zmiennej o nazwie „c”.
Johannes Schaub - litb
Wiem, że ten post jest dość stary, ale czuję potrzebę wtrącenia się. Nie zrywasz „trochę” hermetyzacji, całkowicie ją zepsujesz. protectednie jest bardziej zamknięty niż public. Chcę udowodnić, że się mylę. Wszystko, co musisz zrobić, to napisać zajęcia z chronionym członkiem i zabronić mi ich modyfikowania. Oczywiście klasa nie może być ostateczna, ponieważ cały sens używania obiektu protected jest do dziedziczenia. Albo coś jest zamknięte, albo nie. Nie ma stanu pośredniego.
Taekahn
3

Krótko mówiąc, tak.

Chronione zmienne składowe umożliwiają dostęp do zmiennej z dowolnych podklas, a także z dowolnych klas w tym samym pakiecie. Może to być bardzo przydatne, szczególnie w przypadku danych tylko do odczytu. Nie sądzę, aby były one kiedykolwiek potrzebne, ponieważ każde użycie chronionej zmiennej składowej można replikować przy użyciu prywatnej zmiennej składowej oraz kilku metod pobierających i ustawiających.

Jay Stramel
źródło
1
I odwrotnie, prywatne zmienne członkowskie również nigdy nie są potrzebne; public jest wystarczający do dowolnego użycia.
Alice,
3

Tak dla przypomnienia, w punkcie 24 „Wyjątkowego C ++” w jednym z przypisów Sutter mówi: „Nigdy nie napisałbyś klasy, która ma publiczną lub chronioną zmienną składową. Prawda? (Niezależnie od kiepskiego przykładu podanego przez niektóre biblioteki .) ”

hAcKnRoCk
źródło
2

Szczegółowe informacje na temat modyfikatorów dostępu do .Net można znaleźć tutaj

Chronione zmienne składowe nie mają żadnych rzeczywistych zalet ani wad, jest to kwestia tego, czego potrzebujesz w konkretnej sytuacji. Ogólnie przyjęta praktyka polega na deklarowaniu zmiennych składowych jako prywatnych i umożliwieniu dostępu z zewnątrz poprzez właściwości. Ponadto niektóre narzędzia (np. Niektóre mapery O / R) oczekują, że dane obiektu będą reprezentowane przez właściwości i nie rozpoznają publicznych lub chronionych zmiennych składowych. Ale jeśli wiesz, że chcesz, aby twoje podklasy (i TYLKO twoje podklasy) miały dostęp do określonej zmiennej, nie ma powodu, aby nie deklarować jej jako chronionej.

Manu
źródło
Chęć, aby podklasy miały dostęp do zmiennej, bardzo różni się od chęci, aby mogły ją swobodnie modyfikować . To jeden z głównych argumentów przeciwko chronionym zmiennym: teraz twoja klasa bazowa nie może zakładać, że żaden z jej niezmienników jest trzymany, ponieważ każda klasa pochodna może zrobić absolutnie wszystko z chronionymi składowymi. To główny argument przeciwko nim. Jeśli potrzebują tylko dostępu do danych, ... napisz akcesor. : P (Używam zmiennych chronionych, chociaż prawdopodobnie więcej niż powinienem, i spróbuję zmniejszyć!)
underscore_d