Jeśli zmienna ma getter i setter, czy powinna być publiczna?


23

Mam klasę ze zmienną, która jest prywatna, a klasa ma getter i setter dla tej zmiennej. Dlaczego nie upublicznić tej zmiennej?

Jedyny przypadek, który uważam, że musisz użyć programów pobierających i ustawiających, to konieczność wykonania operacji oprócz zestawu lub get. Przykład:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
Gettery i setery są tutaj do osłony. Pewnego dnia możesz chcieć to zrobić this->variable = x + 5lub wywołać UpdateStatisticsfunkcję settera, w takich przypadkach classinstancea->variable = 5spowoduje to problemy.
Koder

1
Innym przykładem jest: set_inch, set_centimeter, get_inch, get_centimeter, niektóre straszne czyny.
rwong

Tak, metody pobierające i ustawiające pomagają kontrolować zmienne instancji w sposób, w jaki chcesz, aby były kontrolowane.
developerdoug

7
@ Koder: zawsze możesz dodać swój getter / setter, gdy zajdzie taka potrzeba? Czy jest to coś, czego nie można łatwo zrobić w C ++ (moje doświadczenie to głównie Delphi)?
Marjan Venema

To pytanie: programmers.stackexchange.com/questions/21802/... może być interesujące.
Winston Ewert

Odpowiedzi:


21

Czy słyszałeś kiedyś o nieruchomości?

Właściwość to pole, które ma „wbudowane” akcesoria (pobierające i ustawiające). Na przykład Java nie ma właściwości, ale zaleca się zapisanie programów pobierających i ustawiających w polu prywatnym. C # ma właściwości.

Dlaczego więc potrzebujemy getterów i seterów? Zasadniczo potrzebujemy go do ochrony / osłony pola. Na przykład nie uzyskujesz dostępu do pola w odwołaniu do pamięci, uzyskujesz dostęp do metody, która następnie zmieni pole (odwołanie). Ta metoda jest w stanie wykonać pewne operacje, których użytkownik nie chce poznać ( zachowanie enkapsulujące ), jak w twoim przykładzie. Wyobraź sobie na przykład, że kilkanaście klas korzysta z twojego pola publicznego i musisz zmienić sposób, w jaki jest on używany ... Musisz spojrzeć na każdą z tych klas, aby zmienić sposób, w jaki korzystają z pola ... Nie więc „OOlysh”.

Ale na przykład jeśli masz pole boolowskie o nazwie martwe. Powinieneś pomyśleć dwa razy, zanim zadeklarujesz setDead i isDead. Powinieneś napisać akcesory, które są czytelne dla człowieka , na przykład kill () zamiast setDead.

Istnieje jednak wiele frameworków, które zakładają, że przestrzegasz konwencji nazewnictwa JavaBean (tutaj mówię o Javie), dlatego w takich przypadkach powinieneś zadeklarować wszystkie pobierające i ustawiające po konwertowaniu nazw.


2
Teraz „czytelny dla człowieka” jest wreszcie argumentem, że mogę „grok”. Wszystkie inne argumenty, które zwykle słyszę, są pewnego rodzaju korektami na przyszłość, które równie łatwo można rozwiązać, gdy zajdzie taka potrzeba ...
Marjan Venema

13
Twoja pogarda dla „sprawdzania przyszłości” jest powszechna, ale myląca. Tak, możesz zająć się takimi zmianami, gdy zajdzie taka potrzeba, jeśli jesteś jedynym klientem tego kodu . Jeśli nigdy nie opublikujesz swojego rozwiązania dla klientów zewnętrznych, możesz po prostu zaciągnąć dług techniczny i nikt nie musi o tym wiedzieć. Ale projekty wewnętrzne mają zwyczaj upublicznienia, kiedy najmniej się tego spodziewasz, a potem biada tobie.
Kilian Foth

2
Naprawdę podnosisz fantastyczny punkt. kill nie jest setem / get, to akcja. Całkowicie ukrywa implementację - to sposób, w jaki kodujesz OO. Z drugiej strony właściwości są tak samo głupie, jak setery / getters - głupsze, nawet jeśli prosta składnia zachęca ludzi do dodawania ich do zmiennych, kiedy nawet nie są potrzebne (OO-Furbar czeka na zdarzenie). Kto by pomyślał, aby użyć kill (), gdy mogliby po prostu dodać tag „property” do swojej martwej flagi?
Bill K

1
Pytanie oznaczone jest pytaniem C ++. C ++ nie obsługuje właściwości, więc dlaczego miałbyś to opublikować?
Thomas Eding,

1
@ThomasEding: C ++ jest dość wyjątkowy, ponieważ ma nadrzędne operatory przypisania. Chociaż C ++ nie używa terminu „właściwość” w taki sam sposób, jak języki takie jak C # lub VB.NET, koncepcja wciąż istnieje. W C ++ możliwe jest użycie przeciążenia operatora, foo.bar = biz.baz+5aby wywołać funkcję bizdo oceny baz, a następnie dodać pięć do niej i wywołać funkcję w foocelu ustawienia barwartości wynikowej. Takie przeciążenia nie są nazywane „właściwościami”, ale mogą służyć temu samemu celowi.
supercat

25

To nie jest najpopularniejsza opinia, ale nie widzę dużej różnicy.

Setery i gettery to dość zły pomysł. Zastanawiałem się nad tym i szczerze mówiąc, w praktyce nie mogę znaleźć różnicy między ustawiającym / pobierającym właściwość a zmienną publiczną.

W TEORII seter i getter lub właściwość dodają miejsce do podjęcia dodatkowych działań, gdy zmienna jest ustawiona / otrzymana i teoretycznie izolują twój kod od zmian.

W rzeczywistości rzadko widzę seterów i getterów używanych do dodawania akcji, a kiedy chcesz dodać akcję, chcesz dodać ją do WSZYSTKICH setterów lub getterów klasy (np. Rejestrowanie), co powinno sprawić, że pomyślisz, że powinna być lepszym rozwiązaniem.

Jeśli chodzi o izolowanie decyzji projektowych, jeśli zmienisz int na długi, nadal musisz zmienić seterów i przynajmniej sprawdzić każdą linię, która ma do nich dostęp ręcznie - nie ma tam dużej izolacji.

W każdym razie domyślnie należy unikać klas zmiennych, więc dodanie settera powinno być ostatecznością. Jest to ograniczane przez wzorzec konstruktora, w którym można ustawić wartość, dopóki obiekt nie znajdzie się w pożądanym stanie, wtedy klasa może stać się niezmienna, a Twoi seterzy będą zgłaszać wyjątki.

Co do getterów - wciąż nie mogę znaleźć dużej różnicy między getterem a publiczną zmienną końcową. Problem w tym, że w obu przypadkach jest to zły OO. Nie powinieneś prosić o wartość z obiektu i operować na nim - powinieneś prosić obiekt o wykonanie dla ciebie operacji.

Nawiasem mówiąc, w żaden sposób nie opowiadam się za zmiennymi publicznymi - mówię, że settery i gettery (a nawet właściwości) są zbyt bliskie, by już były zmiennymi publicznymi.

Dużym problemem jest po prostu to, że ludzie, którzy nie są programistami OO, są zbyt kuszeni, aby używać setterów i getterów do przekształcania obiektów w kule własności (struktury), które są przekazywane i obsługiwane, co jest odwrotnością działania kodu zorientowanego obiektowo.


Jedną dobrą rzeczą oprócz innych odpowiedzi tutaj, dotyczących gettera jest to, że mogę dodać w nim kilka sprawdzeń poprawności / konwersji, zanim rzeczywista zmienna prywatna przyjmie jej wartość.
WinW

1
@Kiran prawda, ale jeśli ustawisz to w konstruktorze, możesz mieć sprawdzanie poprawności ORAZ masz niezmienną klasę. Dla seterów nie mówię, że publicznie zapisywalna zmienna jest dobra, mówię, że nawet setery są złe. Dla osób pobierających mogę uznać publiczną zmienną końcową za realną alternatywę.
Bill K

W niektórych językach, takich jak C #, właściwości nie są binarnie kompatybilne ze zmiennymi publicznymi, więc jeśli uczynisz zmienną publiczną częścią swojego interfejsu API, ale chcesz dodać weryfikację, przerwiesz program klienta podczas konwersji własność. Od samego początku lepiej uczynić go własnością publiczną.
Robert Harvey

@RobertHarvey To wciąż oznacza, że ​​udostępniasz właściwości. Chodzi mi o to, że należy unikać ujawniania właściwości, a jeśli to konieczne, powinieneś spróbować ograniczyć je do osób pobierających i uczynić swoją klasę niezmienną. Jeśli twoja klasa jest niezmienna, to dlaczego miałbyś potrzebować kodu w getterze - i dlatego wynika, że ​​równie dobrze możesz nie mieć gettera. Jeśli nie jesteś w stanie napisać klasy bez ujawnienia właściwości zmiennych, to tak, seter jest zawsze lepszy niż zapisywalna zmienna publiczna (a postrzelenie w stopę jest zawsze lepsze niż dźgnięcie w jelito).
Bill K

1
Zmienny stan jest akceptowalny, ale ponownie powinieneś poprosić swój obiekt, aby coś dla ciebie zrobił, a nie rób coś w stosunku do swojego obiektu - dlatego gdy mutujesz swój stan, seter nie jest świetnym sposobem na to, spróbuj napisać metodę biznesową z logiką w nim zamiast tego. Na przykład „przenieś (Up5) zamiast setZLocation (getZLocation () + 5)”.
Bill K,

8

Użytkownik programów pobierających i ustawiających stosuje się do zasady hermetyzacji . Pozwoli ci to zmienić sposób działania wewnątrz klasy i utrzymać wszystko w działaniu.

Na przykład, jeśli 3 inne obiekty wywołują, foo.baraby uzyskać wartość paska, a ty zdecydujesz się zmienić nazwę paska tak daleko, że masz problem na rękach. Gdyby wywoływane obiekty foo.barmusiałyby zmienić wszystkie klasy, które to mają. Jeśli używany jest setter / getter, nie masz nic do zmiany. Inną możliwością jest zmiana typu zmiennej, w którym to przypadku wystarczy dodać kod transformujący do gettera / settera i wszystko jest w porządku.


1
+1 - zmienną składową jest implementacja, getter i setter są interfejsem. Tylko interfejs powinien być publiczny. Również nazwy pobierające i ustawiające powinny mieć sens w kontekście abstrakcji, niezależnie od tego, czy leży u ich podstaw prosta zmienna składowa, czy też inna implementacja. Tam wyjątki - przypadki, w których podsystem zbudowany z połączonych szczelnie klas to najprostszy sposób - ale to tylko popycha granicę danych kryjówkę na wyższy poziom tak, aby klasy, która reprezentuje cały podsystem.
Steve314

Czy nie mogę po prostu przedstawić gettera i / lub setera, gdy muszę zmienić pracę w klasie? Jest to coś, co łatwo zrobić w Delphi, ale może nie w innych językach?
Marjan Venema

@ marjan Pewnie, możesz wprowadzić getter / setter w dowolnym momencie później. Problem polega na tym, że musisz cofnąć się i zmienić CAŁY kod, który używał pola publicznego zamiast getter / setter. Nie jestem pewien, czy jestem tym, który jest zdezorientowany, czy ty. 0.o
Pete

3
Osobiście uważam, że to trochę fałszywy argument. Jeśli zmienisz znaczenie zmiennej, ale zmienna ma getter i setter, to nie ma znaczenia, jak się nazywa. To nie jest widoczna część interfejsu. O wiele bardziej prawdopodobne jest, że zechcesz zmienić nazwę modułów pobierających i ustawiających, aby wskazywać klientom zmianę lub wyjaśnienie znaczenia enkapsulowanej zmiennej. W tym ostatnim przypadku nie ma lepszej pozycji niż zmienna publiczna. Jest to całkiem niezależne od argumentu, że zmienna z modułami pobierającymi i ustawiającymi jest bardziej widoczna niż hermetyzowana.
CB Bailey

1
W każdym przypadku operacja refaktoryzacji jest praktycznie darmowa, więc tak naprawdę nie kupuję tego. Plus, to bardzo zła forma używania setera lub gettera, chyba że jest to absolutnie potrzebne - w końcu ludzie, którzy nie rozumieją tej koncepcji, automatycznie dodają setery i gettery dla członków zamiast wtedy, gdy są potrzebne, co jest po prostu kapaniem nasion zła w całej bazie kodu.
Bill K

5

Korzystanie z metod pobierających i ustawiających umożliwia także kontrolowanie zawartości przechowywanej w określonej zmiennej. Jeśli treść musi być określonego typu lub wartości, częścią kodu ustawiającego może być upewnienie się, że nowa wartość spełnia te wymagania. Jeśli zmienna jest publiczna, nie możesz upewnić się, że te wymagania są spełnione.

Takie podejście sprawia również, że Twój kod jest łatwiejszy do dostosowania i zarządzania. Znacznie łatwiej jest wprowadzać zmiany w architekturze klasy, jeśli masz funkcje, które ukrywają tę architekturę przed wszystkimi innymi klasami lub funkcjami, które korzystają z tej klasy. Wspomniana już zmiana nazwy zmiennej jest tylko jedną z wielu wielu zmian, które są znacznie łatwiejsze do wprowadzenia, jeśli masz funkcje takie jak gettery i setters. Ogólnym pomysłem jest zachowanie jak największej prywatności, szczególnie zmiennych klas.


Wielkie dzięki! Myślałem o tej samej sprawie, o której wspomniałeś. Jeśli chcę, aby zmienna była zawarta w [0,1], powinienem sprawdzić przychodzącą wartość w jej ustawniku :)
Oni

Gdy jesteś jedynym programistą pracującym nad projektem, łatwo jest pomyśleć, że takie rzeczy są bezużyteczne, ale kiedy znajdziesz się w pracy z zespołem, przekonasz się, że przyzwyczajenie się do takich technik przyda się bardzo i pomoże ci uniknąć wielu błędów na drodze.
Kenneth

Jeśli używasz C #, użyj właściwości, możesz użyć właściwości, aby ustawić instancję prywatną, jeśli to konieczne, w oparciu o logikę w części ustawiającej właściwości.
developerdoug

2

Mówisz „Jedynym przypadkiem, który myślę, że musisz użyć getterów i setterów, jest to, że musisz wykonać jakąś operację poza setem lub get.”.

Powinieneś użyć getterów i seterów, jeśli w pewnym momencie w przyszłości będziesz mógł zajść potrzeba wykonania operacji poza ustawieniem i pobieraniem i nie chcesz zmieniać tysięcy wierszy kodu źródłowego, kiedy to się stanie.

Powinieneś używać programów pobierających i ustawiających, jeśli nie chcesz, aby ktoś pobierał adres zmiennej i przekazywał ją, z katastrofalnymi konsekwencjami, jeśli zmienna ta może zostać zmieniona bez podania kodu źródłowego, a nawet po tym, jak obiekt przestał istnieć .


Twój ostatni akapit stanowi doskonały punkt, który tnie na dwa sposoby: wymaganie użycia programów pobierających i ustawiających zapobiegnie przyjęciu adresu przez inny kod. W przypadkach, w których przyniosłoby to znaczące korzyści i nieliczne wady związane z pobieraniem adresów przez kod zewnętrzny, takie ograniczenie może być złe. jeśli byłoby niewiele korzyści i poważnych wad pozwalających na takie zachowanie, ograniczenie tego może być dobrą rzeczą.
supercat

1

Po pierwsze, wyjaśnijmy paradygmat.

  • Struktury danych -> układ pamięci, po którym można przemieszczać się i manipulować przy pomocy odpowiednio poinformowanych funkcji.
  • Obiekty -> samodzielny moduł, który ukrywa swoją implementację i zapewnia interfejs, przez który można się komunikować.

Gdzie przydatny jest getter / setter?

Czy metody pobierające / ustawiające są przydatne w strukturach danych? Nie.

Struktura danych to specyfikacja układu pamięci, która jest wspólna i manipulowana przez rodzinę funkcji.

Zasadniczo każda stara nowa funkcja może nadejść i manipulować strukturą danych, jeśli robi to w taki sposób, że inne funkcje nadal ją rozumieją, wówczas funkcja dołącza do rodziny. W przeciwnym razie jest to nieuczciwa funkcja i źródło błędów.

Nie zrozumcie mnie źle, że może istnieć kilka rodzin funkcji walczących o tę strukturę danych ze snitchem, płaszczem i podwójnym agentem wszędzie. To dobrze, gdy każdy z nich ma własną strukturę danych, z którą można się bawić, ale kiedy się nią dzielą ... wyobraź sobie, że kilka rodzin przestępczych nie zgadza się z polityką, może stać się naprawdę szybko bałaganem.

Biorąc pod uwagę bałagan, jaki rodziny rozszerzonych funkcji mogą osiągnąć, czy istnieje sposób na zakodowanie struktury danych, aby nieuczciwe funkcje nie zepsuły wszystkiego? Tak, nazywają się Obiektami.

Czy gettery / settery są przydatne w Objectach? Nie.

Cały sens zawijania struktury danych w obiekcie polegał na tym, aby nie istniały żadne nieuczciwe funkcje. Jeśli funkcja chciała dołączyć do rodziny, najpierw musiała zostać dokładnie sprawdzona, a następnie stać się częścią obiektu.

Punktem / celem gettera i setera jest umożliwienie funkcjom poza obiektem bezpośredniej zmiany układu pamięci obiektu. To brzmi jak otwarte drzwi dla łotrzyków ...

The Edge Case

Istnieją dwie sytuacje, w których publiczny getter / setter ma sens.

  • Część struktury danych w obiekcie jest zarządzana przez obiekt, ale nie jest kontrolowana przez obiekt.
  • Interfejs opisujący abstrakcyjną strukturę danych na wysokim poziomie, w której niektóre elementy nie powinny mieć kontroli nad obiektem implementującym.

Kontenery i interfejsy kontenerów są idealnymi przykładami obu tych dwóch sytuacji. Kontener wewnętrznie zarządza strukturami danych (lista połączona, mapa, drzewo), ale zapewnia kontrolę nad określonym elementem wszystkim i różnym. Interfejs to streszcza i całkowicie ignoruje implementację i opisuje tylko oczekiwania.

Niestety wiele implementacji źle to rozumie i definiuje interfejs tego rodzaju obiektów, aby zapewnić bezpośredni dostęp do rzeczywistego obiektu. Coś jak:

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

To jest zepsute. Wdrożenia kontenera muszą jawnie przekazać kontrolę nad swoimi elementami wewnętrznymi komukolwiek, kto ich używa. Nie widziałem jeszcze języka o zmiennej wartości, w którym jest to w porządku (języki z semantyką wartości niezmiennej są z definicji w porządku z perspektywy niszczenia danych, ale niekoniecznie z perspektywy szpiegowania danych).

Możesz poprawić / poprawić gettery / setter, używając tylko semantyki kopiowania lub używając proxy:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

Prawdopodobnie nieuczciwa funkcja mogłaby nadal odgrywać chaos (przy wystarczającym wysiłku większość rzeczy jest możliwa), ale semantyka kopiowania i / lub proxy zmniejsza prawdopodobieństwo wystąpienia wielu błędów.

  • przelewowy
  • niedomiar
  • interakcje z elementem podrzędnym są sprawdzane pod kątem typu / sprawdzalne pod względem typu (w typie tracenia języków jest to dobrodziejstwo)
  • Rzeczywisty element może, ale nie musi, być rezydentem pamięci.

Osoby pobierające / ustawiające prywatne

To ostatni bastion pobierających i ustawiających pracujących bezpośrednio na tym typie. W rzeczywistości nie nazwałbym nawet tych pobierających i ustawiających, ale akcesoria i manipulatory.

W tym kontekście czasami manipulowanie określoną częścią struktury danych zawsze / prawie zawsze / ogólnie wymaga określonego przechowywania ksiąg. Powiedzmy, że podczas aktualizacji katalogu głównego drzewa pamięć podręczna musi zostać wyczyszczona lub podczas uzyskiwania dostępu do zewnętrznego elementu danych należy uzyskać / zwolnić blokadę. W takich przypadkach sensowne jest zastosowanie zasady SUCHEJ i spakowanie tych działań razem.

W kontekście prywatnym nadal możliwe jest, aby inne funkcje w rodzinie pomijały te „moduły pobierające i ustawiające” oraz manipulowały strukturą danych. Dlatego myślę o nich bardziej jako o akcesoriach i manipulatorach. Możesz uzyskać bezpośredni dostęp do danych lub polegać na innym członku rodziny, aby uzyskać właściwą część.

Chronieni Getters / Setters

W kontekście chronionym nie różni się tak bardzo od kontekstu publicznego. Zagraniczne, prawdopodobnie nieuczciwe funkcje chcą dostępu do struktury danych. Więc nie, jeśli istnieją, działają jak publiczne podmioty pobierające / ustawiające.


0

Z doświadczenia w C ++ Setter / getter jest pomocny w 2 scenariuszach:

  1. jeśli musisz wykonać kontrolę poprawności przed przypisaniem wartości do elementu, takiego jak łańcuchy lub tablica.
  2. może być pomocne, gdy potrzebne jest przeciążenie funkcji, takie jak wartość int, aby przypisać wartość bool, gdy -1 (lub wartości ujemne) jest fałszywe. i odwrotnie dla gettera.

Oprócz tego bezpieczeństwo jest ważnym punktem, ale jego znaczenie jest ograniczone do bardzo niewielu aplikacji programistycznych, takich jak logowanie lub moduły związane z dostępem do bazy danych. Po co zawracać sobie głowę kodowaniem, skoro tylko kilka osób korzysta z tego modułu?

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.