Dlaczego warto korzystać z programów pobierających i ustawiających / akcesorów?


1540

Jaka jest zaleta używania metod pobierających i ustawiających - które tylko pobierają i ustawiają - zamiast po prostu używania pól publicznych dla tych zmiennych?

Jeśli osoby pobierające i ustawiające kiedykolwiek robią coś więcej niż zwykłe polecenie get / set, mogę to szybko rozgryźć, ale nie jestem w 100% pewien, jak:

public String foo;

jest gorszy niż:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

Podczas gdy ten pierwszy wymaga o wiele mniej kodu typu „kocioł”.


6
@Dean J: Duplikuj z wieloma innymi pytaniami: stackoverflow.com/search?q=getters+setters
Asaph

8
Oczywiście oba są równie złe, gdy obiekt nie wymaga zmiany właściwości. Wolę uczynić wszystko prywatnym, a następnie dodać gettery, jeśli są przydatne, i settery, jeśli to konieczne.
Tordek

26
Google „accessors are evil”
OMG Ponies

34
„Akcesoria są złe”, jeśli piszesz kod funkcjonalny lub niezmienne obiekty. Jeśli zdarza ci się pisać stanowe obiekty zmienne, to są one bardzo istotne.
Christian Hayter

Odpowiedzi:


980

W rzeczywistości istnieje wiele dobrych powodów, aby rozważyć użycie akcesoriów zamiast bezpośredniego ujawnienia pól klasy - poza samym argumentem hermetyzacji i ułatwieniem przyszłych zmian.

Oto niektóre z powodów, o których wiem:

  • Hermetyzacja zachowań związanych z uzyskaniem lub ustawieniem właściwości - pozwala to na późniejsze dodanie dodatkowej funkcjonalności (np. Walidacji).
  • Ukrywanie wewnętrznej reprezentacji właściwości podczas eksponowania właściwości za pomocą alternatywnej reprezentacji.
  • Izolowanie interfejsu publicznego od zmian - pozwalając, aby interfejs publiczny pozostawał stały, podczas gdy implementacja zmienia się bez wpływu na istniejących konsumentów.
  • Kontrolowanie semantyki właściwości życia i zarządzania pamięcią (usuwanie) - szczególnie ważne w niezarządzanych środowiskach pamięci (takich jak C ++ lub Objective-C).
  • Zapewnienie debugującego punktu przechwytywania, gdy właściwość zmienia się w czasie wykonywania - debugowanie, kiedy i gdzie właściwość zmieniona na określoną wartość może być dość trudna bez niektórych języków.
  • Poprawiona interoperacyjność z bibliotekami zaprojektowanymi do działania przeciwko modułom pobierającym / ustawiającym właściwości - przychodzą na myśl kpiny, serializacja i WPF.
  • Zezwolenie spadkobiercom na zmianę semantyki zachowania się i ujawnienia właściwości przez przesłonięcie metod getter / setter.
  • Zezwalanie na przekazywanie gettera / settera jako wyrażeń lambda zamiast wartości.
  • Programy pobierające i ustawiające mogą zezwalać na różne poziomy dostępu - na przykład get może być publiczny, ale zestaw może być chroniony.

59
+1. Wystarczy dodać: Zezwolenie na leniwe ładowanie. Zezwalanie na kopiowanie podczas zapisu.
NewbiZ

6
@bjarkef: Wierzę, że publicmetody akcesora powodują duplikację kodu i ujawniają informacje. Publiczne urządzenia dostępowe nie są konieczne do zestawiania (serializacji). W niektórych językach (Java) w publicakcesoriach pobierania nie można zmienić typu zwrotu bez rozbijania klas zależnych. Ponadto uważam, że są one sprzeczne z zasadami OO. Zobacz także: stackoverflow.com/a/1462424/59087
Dave Jarvis

15
@sbi: Jedną rzeczą, która jest kupowana za pomocą narzędzia do ustawiania właściwości, nawet w dobrze zaprojektowanym środowisku, jest możliwość powiadamiania obiektu o zmianie obiektu.
supercat

22
@supercat: Ogólnie jednak nie promuję danych publicznych. Powiadomienie o innych obiektach można równie dobrze wykonać z rzeczywistych metod, zapewniając znaczącą abstrakcję na zwykłym kontenerze danych z (mniej lub bardziej) publicznymi polami danych. Zamiast tego plane.turnTo(dir); plane.setSpeed(spd); plane.setTargetAltitude(alt); plane.getBreaks().release();chcę powiedzieć plane.takeOff(alt). To, jakie wewnętrzne pola danych należy zmienić, aby wprowadzić samolot w takingOfftryb, nie jest moim zmartwieniem. I jakie inne obiekty ( breaks) metoda powiadamia, że ​​nie chcę też wiedzieć.
sbi

8
@BenLee: Naprawdę nie wiem, jak inaczej to powiedzieć. „Accessors” to tylko synonim „getters / setters”, aw pierwszych dwóch komentarzach wyjaśniłem, dlaczego są to nadużycia terminu „OOP”. Jeśli potrzebujesz sięgnąć do niego i bezpośrednio manipulować stanem, nie jest to obiekt w sensie OOP tego terminu.
sbi

480

Ponieważ za 2 tygodnie (miesiące, lata), kiedy zdasz sobie sprawę, że twój setter musi zrobić coś więcej niż tylko ustawić wartość, zdasz sobie również sprawę, że nieruchomość była używana bezpośrednio w 238 innych klasach :-)


84
Siedzę i wpatruję się w aplikację na 500 tys. Linii, w której nigdy nie była potrzebna. To powiedziawszy, jeśli raz będzie to potrzebne, zacznie powodować koszmar utrzymania. Wystarczająco dobry, by zaznaczyć dla mnie znacznik wyboru.
Dean J

20
Mogę zazdrościć tylko tobie i twojej aplikacji :-) To powiedziawszy, to naprawdę zależy również od twojego stosu oprogramowania. Delphi, na przykład (i C # - myślę?) Pozwala definiować właściwości jako obywateli pierwszej klasy, w których mogą odczytywać / zapisywać pola bezpośrednio początkowo, ale - w razie potrzeby - robią to również metodami getter / setter. Bardzo wygodne. Niestety, Java nie wspomina o standardzie javabeans, który zmusza cię również do używania programów pobierających / ustawiających.
ChssPly76

14
Chociaż jest to rzeczywiście dobry powód do korzystania z akcesoriów, wiele środowisk programistycznych i edytorów oferuje teraz wsparcie dla refaktoryzacji (w IDE lub jako bezpłatne dodatki), co nieco zmniejsza wpływ problemu.
LBushkin

62
brzmi jak optymalizacja
przedwczesna

41
Pytanie, które musisz zadać, zastanawiając się, czy zaimplementować metody pobierające i ustawiające, brzmi: dlaczego użytkownicy klasy mieliby w ogóle mieć dostęp do wewnętrznych elementów klasy? Tak naprawdę nie ma znaczenia, czy robią to bezpośrednio, czy osłaniani cienką pseudo warstwą - jeśli użytkownicy muszą uzyskać dostęp do szczegółów implementacji, oznacza to, że klasa nie oferuje wystarczającej abstrakcji. Zobacz także ten komentarz .
sbi

357

Pole publiczne nie jest gorsze niż para getter / setter, która nie robi nic poza zwrotem pola i przypisaniem do niego. Po pierwsze, jasne jest, że (w większości języków) nie ma żadnej różnicy funkcjonalnej. Każda różnica musi wynikać z innych czynników, takich jak łatwość konserwacji lub czytelność.

Często wymieniana zaleta par getter / setter nie jest. Istnieje twierdzenie, że możesz zmienić implementację, a twoi klienci nie muszą być ponownie kompilowani. Podobno setery pozwalają później dodawać funkcje takie jak sprawdzanie poprawności, a Twoi klienci nawet nie muszą o tym wiedzieć. Jednak dodanie sprawdzania poprawności do setera jest zmianą jego warunków wstępnych, naruszeniem poprzedniej umowy , która po prostu brzmiała: „możesz tu wstawić wszystko i możesz uzyskać to samo później od gettera”.

Teraz, gdy złamałeś umowę, zmiana każdego pliku w bazie kodu jest czymś, czego powinieneś chcieć, a nie unikać. Jeśli tego unikniesz, przyjmujesz, że cały kod zakładał, że umowa na te metody była inna.

Gdyby nie taka była umowa, interfejs pozwalał klientom na umieszczenie obiektu w niepoprawnych stanach. To jest dokładnie odwrotność enkapsulacji. Jeśli to pole nie mogło tak naprawdę być ustawione na coś od samego początku, dlaczego nie było tam sprawdzania poprawności od samego początku?

Ten sam argument dotyczy innych rzekomych zalet tych par getter / setter: jeśli później zdecydujesz się zmienić ustawianą wartość, zrywasz kontrakt. Jeśli zastąpisz domyślną funkcjonalność w klasie pochodnej, w sposób wykraczający poza kilka nieszkodliwych modyfikacji (takich jak rejestrowanie lub inne nieobserwowalne zachowanie), zrywasz kontrakt klasy podstawowej. Jest to naruszenie zasady substytucyjności Liskowa, która jest postrzegana jako jedna z zasad OO.

Jeśli klasa ma te głupie pobierające i ustawiające dla każdego pola, to jest to klasa, która nie ma żadnych niezmienników, żadnej umowy . Czy to naprawdę projekt obiektowy? Jeśli jedyną klasą są te pobierające i ustawiające, to tylko głupi posiadacz danych, a głupi posiadacze danych powinni wyglądać jak głupi posiadacze danych:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

Dodanie par getter / setter tranzytowych do takiej klasy nie dodaje żadnej wartości. Inne klasy powinny zapewniać znaczące operacje, a nie tylko operacje, które pola już zapewniają. W ten sposób możesz definiować i utrzymywać przydatne niezmienniki.

Klient : „Co mogę zrobić z obiektem tej klasy?”
Projektant : „Możesz odczytać i zapisać kilka zmiennych.”
Klient : „Och ... fajnie, tak myślę?”

Istnieją powody, aby używać pobierających i ustawiających, ale jeśli te powody nie istnieją, tworzenie par pobierających / ustawiających w imię fałszywych bogów hermetyzacji nie jest dobrą rzeczą. Prawidłowe powody, dla których gettery lub settery obejmują rzeczy często wymieniane jako potencjalne zmiany, które możesz wprowadzić później, takie jak walidacja lub różne wewnętrzne reprezentacje. A może wartość powinna być czytelna dla klientów, ale nie do zapisu (na przykład odczytanie wielkości słownika), więc prosty getter jest dobrym wyborem. Ale powody te powinny istnieć przy dokonywaniu wyboru, a nie tylko jako potencjalna rzecz, której możesz chcieć później. To jest przykład YAGNI ( You Ain't Go Need Need ).


12
Świetna odpowiedź (+1). Moją jedyną krytyką jest to, że zajęło mi kilka lektur, aby dowiedzieć się, w jaki sposób „walidacja” w ostatnim akapicie różni się od „walidacji” w pierwszych kilku (które wyrzuciłeś w tym drugim przypadku, ale awansowałeś w pierwszym); dostosowanie sformułowania może w tym pomóc.
Wyścigi lekkości na orbicie

14
To świetna odpowiedź, ale niestety obecna era zapomniała, czym jest „ukrywanie informacji” i do czego służy. Nigdy nie czytali o niezmienności, a w poszukiwaniu najbardziej zwinnych nigdy nie narysowali diagramu przejścia między stanami, który określałby, jakie są prawne stany obiektu, a co za tym idzie.
Darrell Teague,

2
Kluczową obserwacją jest to, że osoby pobierające są znacznie bardziej przydatne niż osoby ustawiające. Moduł pobierający może zwrócić obliczoną wartość lub buforowaną wartość obliczoną. Seter może jedynie dokonać weryfikacji i zmienić privatepole. Jeśli klasa nie ma elementów ustawiających, łatwo jest uczynić ją niezmienną.
Raedwald,

7
Ee, chyba nie wiesz, czym jest niezmiennik klasowy. Jeśli jest to nietrywialna struktura, to znaczy, że ma niezmienniki do utrzymania i po prostu nie można jej zaprojektować bez nich. „Wystąpił błąd, jeden członek został nieprawidłowo zaktualizowany”. również brzmi prawie tak: „Aktualizacja członka naruszyła niezmienniki klas”. Dobrze zaprojektowana klasa nie pozwala kodowi klienta naruszać jego niezmienników.
R. Martinho Fernandes,

5
+1 dla „głupich posiadaczy danych powinno wyglądać jak głupich posiadaczy danych” Niestety, wydaje się, że wszyscy obecnie projektują wszystko wokół głupich posiadaczy danych, a wiele frameworków nawet tego wymaga ...
Joeri Hendrickx

92

Wiele osób mówi o zaletach getterów i seterów, ale chcę grać w adwokata diabła. W tej chwili debuguję bardzo duży program, w którym programiści postanowili zrobić wszystko, aby pobrać i ustawić. To może wydawać się miłe, ale to koszmar inżynierii odwrotnej.

Powiedzmy, że przeglądasz setki linii kodu i natrafiasz na to:

person.name = "Joe";

Jest to pięknie po prostu kawałek kodu, dopóki nie uświadomisz sobie, że jest on ustawiaczem. Teraz podążasz za tym ustawiaczem i odkrywasz, że ustawia on także person.firstName, person.lastName, person.isHuman, person.hasReallyCommonFirstName i wywołuje person.update (), który wysyła zapytanie do bazy danych itp. Och, to gdzie miał miejsce wyciek pamięci.

Zrozumienie lokalnego fragmentu kodu na pierwszy rzut oka jest ważną właściwością dobrej czytelności, którą pobierają i pobudzają programy ustawiające. Dlatego staram się ich unikać, kiedy tylko mogę, i minimalizować to, co robią, kiedy ich używam.


8
Tak. Obecnie refaktoryzujemy dużą bazę kodu, co było koszmarem. Osoby pobierające i ustawiające robią o wiele za dużo, włączając w to dzwonienie do innych bardzo zajętych osób pobierających i ustawiających, które w rezultacie zmniejszają czytelność do zera. Walidacja to świetny powód do korzystania z akcesoriów, ale użycie ich do zrobienia znacznie więcej wydaje się usuwać wszelkie potencjalne korzyści.
Fadecomic

31
Jest to argument przeciwko cukierowi syntaktycznemu, a nie przeciwko seterom w ogóle.
Phil

6
Prawda i prawda, ale ponadto wskazuje, w jaki sposób poszczególne podmioty ustalające właściwości otwierają drzwi dla stanów nieprawidłowych bez absolutnie żadnego lekarstwa na zapewnienie integralności dowolnego obiektu, który pozwala na wyciek abstrakcji.
Darrell Teague,

„Jest to pięknie po prostu kawałek kodu, dopóki nie uświadomisz sobie, że jest on ustawiaczem” - hej, czy był to oryginalny post o Javie lub o C #? :)
Honza Zidek

Całkowicie zgadzam się co do czytelności. To jest całkowicie jasne, co się dzieje, kiedy person.name = "Joe";się nazywa. Informacje nie zawierają żadnych cruftów, podświetlanie składni pokazuje, że jest to pole, a refaktoryzacja jest prostsza (nie trzeba refaktoryzować dwóch metod i pola). Po drugie, wszystkie te zmarnowane cykle mózgowe myślące „getIsValid ()” lub powinno być „isValid ()” itp. Można zapomnieć. Nie potrafię wymyślić singla, gdy getter / setter uratował mnie przed błędem.
Czy

53

W świecie czysto zorientowanym obiektowo getters and setters to straszna anty-wzór . Przeczytaj ten artykuł: Getters / Setters. Zło. Okres . W skrócie, zachęcają programistów do myślenia o obiektach jak o strukturach danych, a tego rodzaju myślenie jest czysto proceduralne (jak w języku COBOL lub C). W języku obiektowym nie ma struktur danych, a jedynie obiekty, które ujawniają zachowanie (nie atrybuty / właściwości!)

Więcej informacji na ich temat można znaleźć w rozdziale 3.5 eleganckich obiektów (moja książka o programowaniu obiektowym).


7
Getters i setters sugerują anemiczny model domeny.
Raedwald,

7
Ciekawy punkt widzenia. Ale w większości kontekstów programowania potrzebujemy struktur danych. Biorąc przykład „Psa” w powiązanym artykule. Tak, nie możesz zmienić wagi psa w świecie rzeczywistym, ustawiając atrybut ... ale new Dog()nie jest psem. Jest to obiekt przechowujący informacje o psie. W przypadku tego zastosowania naturalne jest, że można skorygować nieprawidłowo zarejestrowaną wagę.
Stephen C

3
Cóż, najbardziej ci to oddaję przydatne programy nie muszą modelować / symulować obiektów ze świata rzeczywistego. IMO, tak naprawdę wcale nie chodzi o języki programowania. Chodzi o to, do czego piszemy programy.
Stephen C

3
Prawdziwy świat czy nie, Yegor ma całkowitą rację. Jeśli to, co masz, jest naprawdę „Strukturą” i nie musisz pisać kodu, który odwołuje się do niego z nazwy, umieść go w tablicy mieszającej lub innej strukturze danych. Jeśli musisz napisać dla niego kod, umieść go jako członka klasy i umieść kod, który manipuluje tą zmienną w tej samej klasie, i pomiń setter & getter. PS. chociaż w większości podzielam punkt widzenia Yegora, doszedłem do przekonania, że ​​komponenty z adnotacjami bez kodu są dość użytecznymi strukturami danych - czasem też są potrzebne gettery, setery nigdy nie powinny istnieć.
Bill K

1
Dałem się ponieść emocjom - cała ta odpowiedź, choć poprawna i trafna, nie odnosi się bezpośrednio do pytania. Być może powinien on powiedzieć „Oba ustawiające / pobierające ORAZ zmienne publiczne są niepoprawne” ... Mówiąc wprost, Setery i zapisywalne zmienne publiczne nigdy nie powinny być nigdy używane, podczas gdy pobieracze są prawie takie same jak publiczne zmienne końcowe i czasami są złem koniecznym ale żadna z nich nie jest znacznie lepsza od drugiej.
Bill K

52

Jest wiele powodów. Moim ulubionym jest, gdy musisz zmienić zachowanie lub regulować, co możesz ustawić na zmiennej. Załóżmy na przykład, że masz metodę setSpeed ​​(int speed). Ale chcesz, abyś mógł ustawić maksymalną prędkość 100. Zrobiłbyś coś takiego:

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

Co teraz, jeśli WSZYSTKO w kodzie używałeś pola publicznego, a następnie zdałeś sobie sprawę, że potrzebujesz powyższego wymagania? Baw się dobrze, polując na każde użycie pola publicznego zamiast modyfikować setera.

Moje 2 centy :)


61
Polowanie na każde użycie pola publicznego nie powinno być takie trudne. Ustaw go jako prywatny i pozwól kompilatorowi je znaleźć.
Nathan Fellman

12
to prawda, ale dlaczego sprawia, że ​​jest to trudniejsze niż miało być. Podejście „get / set” jest nadal lepszą odpowiedzią.
Hardryv,

28
@Nathan: Znalezienie innych zastosowań nie jest problemem. Zmiana ich wszystkich jest.
Graeme Perrow,

22
@GraemePerrow konieczność zmiany ich wszystkich jest zaletą, a nie problemem :( A gdybyś miał kod, który zakładał, że prędkość może być wyższa niż 100 (bo wiesz, zanim złamiesz umowę, może!) ( while(speed < 200) { do_something(); accelerate(); })
R. Martinho Fernandes

29
To BARDZO zły przykład! Ktoś powinien zadzwonić: myCar.setSpeed(157);i po kilku liniach speed = myCar.getSpeed();A teraz ... Życzę szczęśliwego debugowania, próbując zrozumieć, dlaczego speed==100to powinno być157
Piotr Aleksander Chmielowski

38

Jedną z zalet akcesoriów i mutatorów jest to, że można przeprowadzić walidację.

Na przykład, jeśli foobyłby publiczny, mógłbym łatwo ustawić go, nulla następnie ktoś inny mógłby spróbować wywołać metodę na obiekcie. Ale już go tam nie ma! Dzięki setFoometodzie mogłem upewnić się, że foonigdy nie był ustawionynull .

Akcesoria i mutatory pozwalają również na enkapsulację - jeśli nie powinieneś widzieć wartości po jej zbiorze (być może jest ustawiony w konstruktorze, a następnie używany przez metody, ale nigdy nie powinien zostać zmieniony), nikt nigdy nie zobaczy. Ale jeśli możesz zezwolić innym klasom na zobaczenie lub zmianę, możesz zapewnić odpowiedni akcesor i / lub mutator.


28

Zależy od twojego języka. Oznaczyłeś to „obiektowo”, a nie „Java”, więc chciałbym zauważyć, że odpowiedź ChssPly76 zależy od języka. Na przykład w Pythonie nie ma powodu, aby używać programów pobierających i ustawiających. Jeśli chcesz zmienić zachowanie, możesz użyć właściwości, która otacza getter i setter wokół podstawowego dostępu do atrybutów. Coś takiego:

class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)

2
Tak, powiedziałem tyle w komentarzu pod moją odpowiedzią. Java nie jest jedynym językiem używającym getters / setters jako narzędzia, podobnie jak Python nie jest jedynym językiem zdolnym do definiowania właściwości. Pozostaje jednak główny punkt - „własność” nie jest tym samym „polem publicznym”.
ChssPly76

1
@jcd - wcale. Definiujesz swój „interfejs” (lepiej byłoby tutaj użyć publicznego interfejsu API), ujawniając swoje publiczne pola. Gdy to zrobisz, nie będzie już powrotu. Właściwości NIE są polami, ponieważ zapewniają mechanizm przechwytywania prób uzyskania dostępu do pól (kierując je do metod, jeśli są one zdefiniowane); to jednak nic więcej niż cukier składniowy zamiast metod getter / setter. Jest to niezwykle wygodne, ale nie zmienia podstawowego paradygmatu - ujawnianie pól bez kontroli nad nimi narusza zasadę enkapsulacji.
ChssPly76

14
@ ChssPly76 - nie zgadzam się. Mam taką samą kontrolę, jak gdyby były właściwościami, ponieważ mogę je tworzyć, kiedy tylko zajdzie taka potrzeba. Nie ma różnicy między właściwością, która korzysta z modułów pobierających i ustawiających, a atrybutem raw, z wyjątkiem tego, że atrybut raw jest szybszy, ponieważ używa języka podstawowego, a nie metod wywoływania. Funkcjonalnie są identyczne. Jedynym sposobem, w jaki można naruszyć enkapsulację, jest to, że uważasz, że nawiasy ( obj.set_attr('foo')) są z natury lepsze od znaków równości ( obj.attr = 'foo'). Dostęp publiczny to dostęp publiczny.
jcdyer

1
@ jcdyer tak dużo kontroli tak, ale nie tak dużo czytelności, inni często błędnie zakładają, że obj.attr = 'foo'po prostu ustawia zmienną bez niczego innego
Timo Huovinen

9
@TimoHuovinen Jak to się różni od użytkownika w Javie, zakładając, że obj.setAttr('foo')„ustawia zmienną tak, aby nic się nie działo”? Jeśli jest to metoda publiczna, to jest to metoda publiczna. Jeśli użyjesz go do osiągnięcia jakiegoś efektu ubocznego i jest on publiczny, to lepiej móc liczyć na wszystko, co działa tak, jakby wystąpił tylko ten zamierzony efekt uboczny (ze wszystkimi innymi szczegółami implementacji i innymi efektami ubocznymi, wykorzystaniem zasobów, cokolwiek innego , ukryte przed obawami użytkownika). W Pythonie nie jest to absolutnie inaczej. Składnia Pythona do osiągnięcia efektu jest po prostu prostsza.
ely

25

Cóż, chcę tylko dodać, że nawet jeśli czasami są one niezbędne do hermetyzacji i bezpieczeństwa twoich zmiennych / obiektów, jeśli chcemy zakodować prawdziwy program obiektowy, musimy przestać nadużywać akcesoriów , bo czasem bardzo zależy na nich, gdy nie jest to naprawdę konieczne i czyni to prawie tak samo, jakbyśmy podali zmienne do wiadomości publicznej.


24

Dzięki, to naprawdę wyjaśniło moje myślenie. Oto (prawie) 10 (prawie) dobrych powodów, by NIE używać getterów i setterów:

  1. Kiedy zdasz sobie sprawę, że musisz zrobić coś więcej niż tylko ustawić i uzyskać wartość, możesz po prostu ustawić pole jako prywatne, co natychmiast powie ci, gdzie bezpośrednio do niego dostęp.
  2. Każda walidacja, którą tam przeprowadzasz, może być pozbawiona kontekstu, co rzadko odbywa się w praktyce.
  3. Możesz zmienić ustawianą wartość - jest to absolutny koszmar, gdy dzwoniący przekazuje ci wartość, którą [horror uderzeniowy] chce, abyś przechowywał JAK JEST.
  4. Możesz ukryć wewnętrzną reprezentację - fantastycznie, więc upewniasz się, że wszystkie te operacje są symetryczne, prawda?
  5. Zaizolowałeś swój interfejs publiczny przed zmianami pod arkuszami - jeśli projektujesz interfejs i nie jesteś pewien, czy bezpośredni dostęp do czegoś jest OK, to powinieneś kontynuować projektowanie.
  6. Niektóre biblioteki tego oczekują, ale niewiele - odbicie, serializacja, fałszywe obiekty działają dobrze z polami publicznymi.
  7. Dziedzicząc tę ​​klasę, możesz zastąpić domyślną funkcjonalność - innymi słowy, NAPRAWDĘ możesz pomylić osoby dzwoniące, nie tylko ukrywając implementację, ale także powodując jej niespójność.

Ostatnie trzy, które właśnie opuszczam (N / A lub D / C) ...


11
Myślę, że kluczowym argumentem jest to, że „jeśli projektowałeś interfejs i nie byłeś pewien, czy bezpośredni dostęp do czegoś jest w porządku, powinieneś był projektować”. Jest to najważniejszy problem z modułami pobierającymi / ustawiającymi: ograniczają klasę do zwykłego pojemnika (mniej lub bardziej) pól publicznych. Jednak w rzeczywistym OOP obiekt jest czymś więcej niż kontenerem pól danych. Zawiera stan i algorytmy do manipulowania tym stanem. Najważniejsze w tym stwierdzeniu jest to, że stan ma być enkapsulowany i manipulowany tylko przez algorytmy dostarczone przez obiekt.
sbi

22

Wiem, że jest trochę za późno, ale myślę, że są ludzie, którzy są zainteresowani wydajnością.

Zrobiłem mały test wydajności. Napisałem klasę „NumberHolder”, która, no cóż, zawiera liczbę całkowitą. Możesz odczytać tę anInstance.getNumber()liczbę całkowitą za pomocą metody gettera lub bezpośrednio uzyskując dostęp do numeru za pomocą anInstance.number. Mój program odczytuje liczbę 1 000 000 000 razy, w obie strony. Proces ten powtarza się pięć razy i czas jest drukowany. Mam następujący wynik:

Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(Czas 1 to bezpośredni sposób, czas 2 to pobierający)

Widzisz, getter jest (prawie) zawsze nieco szybszy. Potem próbowałem z różną liczbą cykli. Zamiast 1 miliona wykorzystałem 10 milionów i 0,1 miliona. Wyniki:

10 milionów cykli:

Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

Przy 10 milionach cykli czasy są prawie takie same. Oto 100 tysięcy (0,1 miliona) cykli:

Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

Również przy różnych ilościach cykli getter jest trochę szybszy niż normalnie. Mam nadzieję, że to ci pomogło.


3
Istnieje „zauważalny” narzut mający wywołanie funkcji, aby uzyskać dostęp do pamięci, zamiast po prostu ładować adres obiektu i dodawać przesunięcie, aby uzyskać dostęp do elementów. Możliwe, że i tak zoptymalizujesz maszynę wirtualną do płaskiej optymalizacji. Niezależnie od tego, wspomniane koszty ogólne nie są warte utraty wszystkich korzyści związanych z getterami / setterami.
Alex

16

Nie używaj ustawiaczy getterów, chyba że jest to potrzebne do bieżącej dostawy. Nie myśl zbyt wiele o tym, co stanie się w przyszłości, jeśli coś, co należy zmienić, to żądanie zmiany w większości aplikacji produkcyjnych, systemów.

Myśl prosto, łatwo, w razie potrzeby dodaj złożoność.

Nie skorzystałbym z niewiedzy właścicieli firm o głębokiej wiedzy technicznej, ponieważ uważam to za słuszne lub podoba mi się to podejście.

Mam ogromny system napisany bez ustawiaczy getterów tylko z modyfikatorami dostępu i niektórymi metodami do sprawdzania poprawności logiki. Jeśli absolutnie potrzebujesz. Użyj czegokolwiek.


16

Używamy getterów i seterów:

  • do ponownego użycia
  • do przeprowadzenia walidacji na późniejszych etapach programowania

Metody pobierające i ustawiające są publicznymi interfejsami umożliwiającymi dostęp do członków klasy prywatnej.


Mantra enkapsulacji

Mantra enkapsulacji ma na celu upublicznienie pól i metod.

Metody Gettera: Możemy uzyskać dostęp do zmiennych prywatnych.

Metody Settera: Możemy modyfikować pola prywatne.

Mimo że metody pobierające i ustawiające nie dodają nowej funkcjonalności, możemy zmienić zdanie, by wrócić później, aby stworzyć tę metodę

  • lepszy;
  • bezpieczniejszy; i
  • szybciej.

Gdziekolwiek można użyć wartości, można dodać metodę zwracającą tę wartość. Zamiast:

int x = 1000 - 500

posługiwać się

int x = 1000 - class_name.getValue();

W kategoriach laika

Reprezentacja klasy „Osoba”

Załóżmy, że musimy przechowywać szczegóły tego Person. To Personma pola name, agei sex. Rozwiązanie to polega na tworzeniu metod name, agei sex. Teraz, jeśli potrzebujemy utworzyć inną osobę, konieczne staje się stworzenie metody name, age,sex wszystko od nowa.

Zamiast tego możemy stworzyć fasolę za class(Person)pomocą metod pobierających i ustawiających. Więc jutro możemy po prostu tworzyć obiekty tej Fasoli, class(Person class)ilekroć potrzebujemy dodać nową osobę (patrz rysunek). Dlatego ponownie wykorzystujemy pola i metody klasy fasoli, co jest znacznie lepsze.


15

Spędziłem sporo czasu zastanawiając się nad sprawą Java i uważam, że prawdziwymi przyczynami są:

  1. Kod interfejsu, a nie implementacja
  2. Interfejsy określają tylko metody, a nie pola

Innymi słowy, jedynym sposobem na określenie pola w interfejsie jest dostarczenie metody zapisu nowej wartości oraz metody odczytu bieżącej wartości.

Te metody to niesławny pobieracz i seter ...


1
Okej, drugie pytanie; w przypadku, gdy jest to projekt, w którym nie eksportujesz źródła do nikogo i masz pełną kontrolę nad źródłem ... czy zyskujesz cokolwiek dzięki programom pobierającym i ustawiającym?
Dziekan J

2
W każdym nietrywialnym projekcie Java musisz kodować interfejsy, aby wszystko było łatwe do zarządzania i testowania (pomyśl o makietach i obiektach proxy). Jeśli używasz interfejsów, potrzebujesz programów pobierających i ustawiających.
Thorbjørn Ravn Andersen

15

Może być przydatny do leniwego ładowania. Powiedzmy, że przedmiot jest przechowywany w bazie danych i nie chcesz go zdobyć, chyba że jest to potrzebne. Jeśli obiekt zostanie pobrany przez moduł pobierający, wówczas obiekt wewnętrzny może być pusty, dopóki ktoś go nie poprosi, a następnie można go pobrać przy pierwszym wywołaniu funkcji pobierającej.

Miałem klasę strony bazowej w projekcie, który został mi przekazany, który ładował niektóre dane z kilku różnych wywołań usługi internetowej, ale dane w tych wywołaniach usługi internetowej nie zawsze były używane na wszystkich stronach potomnych. Usługi sieciowe, ze wszystkimi korzyściami, są pionierami nowych definicji „powolnego”, więc nie musisz nawiązywać połączeń z usługami internetowymi, jeśli nie musisz.

Przeniosłem się z pól publicznych do getters, a teraz getters sprawdzają pamięć podręczną, a jeśli jej nie ma, zadzwoń do serwisu WWW. Dzięki niewielkiemu pakowaniu udało się zapobiec wielu połączeniom z serwisem internetowym.

Tak więc getter ratuje mnie przed próbą ustalenia na każdej stronie podrzędnej, czego będę potrzebować. Jeśli tego potrzebuję, dzwonię do pobierającego, a on szuka go dla mnie, jeśli jeszcze go nie mam.

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }

A zatem, czy pobieracz wzywa setera?
icc97

Dodałem przykładowy kod z tego, jak to zrobiłem w przeszłości - zasadniczo przechowujesz rzeczywistą klasę w chronionym elemencie, a następnie zwracasz ten chroniony element w getrze, inicjując go, jeśli nie został zainicjowany.
quillbreaker


13

Do tej pory brakowało jednego aspektu w odpowiedziach, specyfikacji dostępu:

  • dla członków masz tylko jedną specyfikację dostępu dla ustawień i uzyskiwania
  • dla seterów i pobierających możesz go dostroić i zdefiniować osobno

13

EDYCJA: Odpowiedziałem na to pytanie, ponieważ wiele osób uczy się programowania, a większość odpowiedzi jest bardzo kompetentna technicznie, ale nie jest tak łatwo zrozumieć, jeśli jesteś początkującym. Wszyscy byliśmy początkującymi, więc pomyślałem, że spróbuję swoich sił, by uzyskać bardziej przyjazną odpowiedź dla początkujących.

Dwa główne to polimorfizm i walidacja. Nawet jeśli to tylko głupia struktura danych.

Powiedzmy, że mamy tę prostą klasę:

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}

Bardzo prosta klasa, która przechowuje ilość płynu i jaka jest jego pojemność (w mililitrach).

Co się stanie, gdy to zrobię:

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;

Nie spodziewałbyś się, że to zadziała, prawda? Chcesz, żeby była jakaś kontrola zdrowia psychicznego. Co gorsza, co jeśli nigdy nie podałem maksymalnej pojemności? Och, kochanie, mamy problem.

Ale jest też inny problem. Co jeśli butelki byłyby tylko jednym rodzajem pojemnika? Co gdybyśmy mieli kilka pojemników, wszystkie o pojemnościach i ilościach napełnionych płynem? Gdybyśmy tylko mogli stworzyć interfejs, moglibyśmy pozwolić, aby reszta naszego programu zaakceptowała ten interfejs, a butelki, jerrycany i wszelkiego rodzaju rzeczy po prostu działałyby zamiennie. Czy nie byłoby lepiej? Ponieważ interfejsy wymagają metod, jest to również dobra rzecz.

Skończylibyśmy z czymś takim jak:

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
}

Świetny! A teraz zmieniamy Butelkę na to:

public class Bottle extends LiquidContainer {
  private int capacityMl;
  private int amountFilledMl;

  public Bottle(int capacityMl, int amountFilledMl) {
    this.capacityMl = capacityMl;
    this.amountFilledMl = amountFilledMl;
    checkNotOverFlow();
  }

  public int getAmountMl() {
    return amountFilledMl;
  }

  public void setAmountMl(int amountMl) {
     this.amountFilled = amountMl;
     checkNotOverFlow();
  }
  public int getCapacityMl() {
    return capacityMl;
  }

  private void checkNotOverFlow() {
    if(amountOfWaterMl > capacityMl) {
      throw new BottleOverflowException();
    }
}

Pozostawię definicję BottleOverflowException jako ćwiczenie czytelnikowi.

Teraz zauważ, o ile bardziej jest to solidne. Możemy teraz obsługiwać dowolny rodzaj pojemnika w naszym kodzie, akceptując LiquidContainer zamiast Bottle. A jak te butelki radzą sobie z tego rodzaju rzeczami, wszystko może się różnić. Możesz mieć butelki, które zapisują swój stan na dysk, gdy się zmienia, lub butelki, które oszczędzają w bazach danych SQL lub GNU wiedzą co jeszcze.

A wszystko to może mieć różne sposoby radzenia sobie z różnymi sekcjami. Butelka po prostu sprawdza, a jeśli jest przepełniona, zgłasza wyjątek RuntimeException. Ale to może być niewłaściwa rzecz. (Istnieje użyteczna dyskusja na temat obsługi błędów, ale celowo utrzymuję ją bardzo prosto. Ludzie w komentarzach prawdopodobnie zwrócą uwagę na wady tego uproszczonego podejścia;))

I tak, wydaje się, że przechodzimy od bardzo prostego pomysłu do szybkiego uzyskiwania znacznie lepszych odpowiedzi.

Należy również pamiętać, że nie można zmienić pojemności butelki. Teraz jest osadzony w kamieniu. Możesz to zrobić z int, deklarując to jako ostateczne. Ale jeśli to była lista, możesz ją opróżnić, dodać do niej nowe rzeczy i tak dalej. Nie możesz ograniczyć dostępu do dotykania wnętrzności.

Jest też trzecia rzecz, do której nie wszyscy się zwracali: pobierający i ustawiający używają wywołań metod Oznacza to, że wszędzie wyglądają jak zwykłe metody. Zamiast mieć dziwną specyficzną składnię dla DTO i innych rzeczy, wszędzie masz tę samą rzecz.


2
Dzięki za pierwsze pół przyzwoite wyjaśnienie interfejsów, które przeczytałem, bez żadnych odniesień do „pilotów” lub „samochodów”
djvs

1
„Możesz mieć butelki, które zapisują swój stan na dysk, gdy się zmienia, lub butelki, które oszczędzają w bazach SQL”. Tak ciężko wypróbowałem xD. Ale w każdym razie fajny pomysł na przedstawienie!
Xerus

10

W językach, które nie obsługują „właściwości” (C ++, Java) lub wymagają ponownej kompilacji klientów przy zmianie pól na właściwości (C #), łatwiej jest modyfikować metody get / set. Na przykład dodanie logiki sprawdzania poprawności do metody setFoo nie będzie wymagało zmiany publicznego interfejsu klasy.

W językach obsługujących „prawdziwe” właściwości (Python, Ruby, może Smalltalk?) Nie ma sensu pobierać / ustawiać metod.


1
Re: C #. Jeśli dodasz funkcjonalność do get / set, czy i tak nie wymagałoby to ponownej kompilacji?
parowiec25

@ steamer25: przepraszam, źle napisano. Miałem na myśli, że klienci klasy będą musieli zostać ponownie skompilowani.
John Millikin

5
Dodanie logiki sprawdzania poprawności do metody setFoo nie będzie wymagało zmiany interfejsu klasy na poziomie językowym , ale zmienia rzeczywisty interfejs , inaczej kontrakt, ponieważ zmienia warunki wstępne. Dlaczego miałbym chcieć kompilator nie traktować to jako zerwanie zmianie , gdy jest ona ?
R. Martinho Fernandes

@ R.MartinhoFernandes, jak można rozwiązać ten „zepsuty” problem? Kompilator nie może stwierdzić, czy się psuje, czy nie. Jest to tylko problem, gdy piszesz biblioteki dla innych, ale robisz to jako uniwersalny zOMG, bądź tutaj smokami!
Phil

3
Wymaganie ponownej kompilacji, jak wspomniano w odpowiedzi, jest jednym ze sposobów, w jaki kompilator może uświadomić ci możliwą przełomową zmianę. I prawie wszystko, co piszę, to właściwie „biblioteka dla innych”, ponieważ nie pracuję sam. Piszę kod, który ma interfejsy, których będą używać inni ludzie w projekcie. Co za różnica? Do diabła, nawet jeśli będę użytkownikiem tych interfejsów, dlaczego miałbym utrzymywać mój kod w celu obniżenia standardów jakości? Nie lubię pracować z kłopotliwymi interfejsami, nawet jeśli to ja je piszę.
R. Martinho Fernandes

6

Jedna z podstawowych zasad projektowania OO: Encapsulation!

Daje to wiele korzyści, z których jedną jest to, że możesz zmienić implementację gettera / settera za kulisami, ale każdy konsument o tej wartości będzie kontynuował pracę, dopóki typ danych pozostanie taki sam.


23
Oferta getterów i seterów enkapsulacji jest śmiesznie cienka. Zobacz tutaj .
sbi

Jeśli twój interfejs publiczny stwierdza, że ​​„foo” jest typu „T” i można ustawić na cokolwiek, nigdy nie możesz tego zmienić. Nie możesz później zdecydować, że będzie to typ „Y”, ani nie możesz narzucać reguł, takich jak ograniczenia wielkości. Zatem jeśli masz publiczne get / set, które nie robią nic wiele set / get, nie zyskujesz niczego, czego publiczne pole nie będzie oferować i czyniąc je bardziej kłopotliwym w użyciu. Jeśli masz ograniczenie, takie jak obiekt może być ustawiony na null lub wartość musi mieścić się w zakresie, wtedy tak, wymagana byłaby metoda zbioru publicznego, ale nadal prezentujesz kontrakt z ustawiania tej wartości, która może „ zmiana
thecoshman

Dlaczego umowa nie może się zmienić?
Phil

5
Dlaczego wszystko powinno się kompilować, jeśli zmieniają się umowy?
R. Martinho Fernandes

5

Powinieneś używać pobierających i ustawiających, gdy:

  • Masz do czynienia z czymś, co jest koncepcyjnie atrybutem, ale:
    • Twój język nie ma właściwości (ani jakiegoś podobnego mechanizmu, takiego jak zmienne ślady Tcl), lub
    • Obsługa właściwości Twojego języka nie jest wystarczająca dla tego przypadku użycia lub
    • Konwencje idiomatyczne twojego języka (lub czasami twojego frameworka) zachęcają osoby pobierające lub ustawiające w tym przypadku użycia.

Jest to więc bardzo rzadko ogólne pytanie OO; jest to pytanie specyficzne dla języka, z różnymi odpowiedziami dla różnych języków (i różnych przypadków użycia).


Z punktu widzenia teorii OO pobierający i ustawiający są bezużyteczni. Interfejs twojej klasy jest tym, co robi, a nie stanem. (Jeśli nie, napisałeś niewłaściwą klasę.) W bardzo prostych przypadkach, gdy to, co robi klasa, to po prostu np. Reprezentuje punkt we współrzędnych prostokątnych, * atrybuty są częścią interfejsu; gettery i setery po prostu to chmurnieją. Ale w niczym innym, jak w bardzo prostych przypadkach, ani atrybuty, ani metody pobierające i ustawiające nie są częścią interfejsu.

Innymi słowy: jeśli uważasz, że konsumenci z twojej klasy nie powinni nawet wiedzieć, że masz spamatrybut, a tym bardziej być w stanie go zmienić nie chcąc, to daj imset_spam metody jest ostatnią rzeczą, którą chcesz zrobić.

* Nawet w przypadku tej prostej klasy niekoniecznie trzeba zezwolić na ustawienie wartości xi y. Jeśli jest to naprawdę klasa, nie powinien mieć takich metod translate, rotateitp? Jeśli to tylko klasa, ponieważ twój język nie ma rekordów / struktur / nazwanych krotek, to tak naprawdę nie jest to kwestia OO…


Ale nikt nigdy nie robi ogólnego projektu OO. Robią projektowanie i wdrażanie w określonym języku. W niektórych językach osoby pobierające i ustawiające są dalekie od bezużytecznych.

Jeśli twój język nie ma właściwości, to jedynym sposobem na przedstawienie czegoś, co koncepcyjnie jest atrybutem, ale w rzeczywistości jest obliczone lub sprawdzone itp., Jest użycie metod pobierających i ustawiających.

Nawet jeśli Twój język ma właściwości, mogą wystąpić przypadki, w których są niewystarczające lub nieodpowiednie. Na przykład, jeśli chcesz pozwolić podklasom kontrolować semantykę atrybutu, w językach bez dostępu dynamicznego, podklasa nie może zastąpić obliczonej właściwości atrybutem.

Co do „co jeśli później chcę zmienić implementację?” pytanie (które powtarza się wiele razy w różnych sformułowaniach zarówno w pytaniu PO, jak i zaakceptowanej odpowiedzi): Jeśli tak naprawdę jest to czysta zmiana implementacji, a ty zacząłeś od atrybutu, możesz zmienić go na właściwość bez wpływu na interfejs. Chyba że twój język tego nie obsługuje. Więc to naprawdę tak samo.

Ważne jest również przestrzeganie idiomów używanego języka (lub frameworka). Jeśli napiszesz piękny kod w języku Ruby w języku C #, każdy doświadczony programista w języku C #, inny niż ty, będzie miał problemy z odczytaniem go, a to źle. Niektóre języki mają silniejsze kultury wokół swoich konwencji niż inne. I może nie być przypadkiem, że Java i Python, które znajdują się na przeciwległych krańcach spektrum, jak idiomatyczni są, mają dwie najsilniejsze kultury.

Poza ludzkimi czytelnikami będą biblioteki i narzędzia, które oczekują od ciebie przestrzegania konwencji i utrudniają ci życie, jeśli tego nie zrobisz. Przechwytywanie widgetów Konstruktora interfejsów do niczego poza właściwościami ObjC lub korzystanie z niektórych fałszywych bibliotek Java bez programów pobierających, tylko utrudnia życie. Jeśli narzędzia są dla Ciebie ważne, nie walcz z nimi.



3

Metody pobierające i ustawiające są metodami dostępowymi, co oznacza, że ​​są one ogólnie publicznym interfejsem do zmiany członków klasy prywatnej. Do zdefiniowania właściwości używa się metod pobierających i ustawiających. Dostęp do metod pobierających i ustawiających uzyskuje się jako właściwości poza klasą, nawet jeśli definiuje się je w klasie jako metody. Te właściwości poza klasą mogą mieć inną nazwę niż nazwa właściwości w klasie.

Korzystanie z metod pobierających i ustawiających ma pewne zalety, takie jak możliwość tworzenia członków o wyrafinowanej funkcjonalności, do której można uzyskać dostęp podobnie jak właściwości. Pozwalają również tworzyć właściwości tylko do odczytu i tylko do zapisu.

Mimo że metody pobierające i ustawiające są przydatne, należy uważać, aby ich nie nadużywać, ponieważ między innymi mogą utrudnić utrzymanie kodu w niektórych sytuacjach. Zapewniają również dostęp do implementacji klasy, podobnie jak członkowie publiczni. Praktyka OOP odradza bezpośredni dostęp do nieruchomości w klasie.

Pisząc klasy, zawsze zachęcamy do uczynienia jak największej liczby zmiennych instancji prywatnymi i dodania odpowiednio metod pobierających i ustawiających. Wynika to z tego, że kilka razy możesz nie chcieć pozwalać użytkownikom zmieniać określonych zmiennych w swoich klasach. Na przykład, jeśli masz prywatną metodę statyczną, która śledzi liczbę instancji utworzonych dla określonej klasy, nie chcesz, aby użytkownik modyfikował ten licznik za pomocą kodu. Tylko instrukcja konstruktora powinna zwiększać tę zmienną za każdym razem, gdy zostanie wywołana. W tej sytuacji możesz utworzyć zmienną instancji prywatnej i zezwolić na metodę gettera tylko dla zmiennej licznika, co oznacza, że ​​użytkownicy mogą pobierać bieżącą wartość tylko za pomocą metody getter i nie będą mogli ustawić nowych wartości za pomocą metody setter.


3

Kod ewoluuje . privatejest świetny, gdy potrzebujesz ochrony członka danych . W końcu wszystkie klasy powinny być swego rodzaju „miniprogramami”, które mają dobrze zdefiniowany interfejs , którego nie można po prostu skręcić z elementami wewnętrznymi .

To powiedziawszy, rozwój oprogramowania nie polega na ustaleniu ostatecznej wersji klasy, tak jakbyś naciskał żeliwną statuę przy pierwszej próbie. Podczas pracy z nim kod przypomina bardziej glinę. Ewoluuje wraz z rozwojem i dowiaduje się więcej o problemie, który rozwiązujesz. Podczas programowania klasy mogą ze sobą współdziałać, niż powinny (zależność, którą planujesz uwzględnić), łączyć się ze sobą lub dzielić. Myślę więc, że debata sprowadza się do ludzi, którzy nie chcą pisać religijnie

int getVar() const { return var ; }

Więc masz:

doSomething( obj->getVar() ) ;

Zamiast

doSomething( obj->var ) ;

Jest nie tylko getVar()wizualnie hałaśliwy, ale daje iluzję, która gettingVar()jest w jakiś sposób bardziej złożonym procesem niż w rzeczywistości. To, jak postrzegasz świętość, varjest szczególnie mylące dla użytkownika twojej klasy, jeśli ma on ustawianie passthru - wtedy wygląda na to, że stawiasz te bramy, aby „chronić” coś, co według ciebie jest cenne, (świętość var), ale nawet ty przyznajesz varochronę, nie jest wiele warta, że ​​ktoś może po prostu wejść iset var do jakiejkolwiek wartości, jakiej pragnie, nawet bez zerknięcia na to, co robią.

Więc programuję w następujący sposób (zakładając podejście typu „zwinnego” - tj. Kiedy piszę kod nie wiedząc dokładnie co on będzie robił / nie mam czasu ani doświadczenia, aby zaplanować rozbudowany zestaw interfejsu w stylu wodospadu):

1) Zacznij od wszystkich członków publicznych dla podstawowych obiektów z danymi i zachowaniem. Dlatego w całym moim „przykładowym” kodzie C ++ zauważysz, że używam structzamiast niegoclass wszędzie.

2) Gdy zachowanie wewnętrzne obiektu dla elementu danych staje się wystarczająco złożone (na przykład lubi utrzymywać wewnętrzny std::listporządek w jakiejś kolejności), zapisywane są funkcje typu akcesor. Ponieważ programuję sam, nie zawsze ustawiam członka privateod razu, ale gdzieś poniżej ewolucji klasy członek zostanie „awansowany” do jednego protectedlub drugiego private.

3) Klasy, które są w pełni rozwinięte i mają ścisłe zasady dotyczące swoich elementów wewnętrznych (tj . Dokładnie wiedzą, co robią i nie wolno ci „pieprzyć” (termin techniczny) z ich classelementami wewnętrznymi) otrzymują oznaczenie, domyślni członkowie prywatni, i tylko kilku wybranych członków może być public.

Uważam, że takie podejście pozwala mi uniknąć siedzenia i religijnego pisania getterów / seterów, gdy wielu członków danych migruje, przemieszcza się itp. Na wczesnych etapach ewolucji klasy.


1
„... dobrze zdefiniowany interfejs, którego nie można po prostu wkręcić z elementami wewnętrznymi” i sprawdzanie poprawności w seterach.
Agi Hammerthief,

3

Istnieje dobry powód, aby rozważyć użycie akcesoriów, ponieważ nie ma dziedziczenia własności. Zobacz następny przykład:

public class TestPropertyOverride {
    public static class A {
        public int i = 0;

        public void add() {
            i++;
        }

        public int getI() {
            return i;
        }
    }

    public static class B extends A {
        public int i = 2;

        @Override
        public void add() {
            i = i + 2;
        }

        @Override
        public int getI() {
            return i;
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.add();
        System.out.println(a.i);
        System.out.println(a.getI());
    }
}

Wynik:

0
0
4

3

Gettery i settery są używane do implementacji dwóch podstawowych aspektów programowania obiektowego:

  1. Abstrakcja
  2. Kapsułkowanie

Załóżmy, że mamy klasę Pracowników:

package com.highmark.productConfig.types;

public class Employee {

    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
      return firstName;
    }
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
         this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName(){
        return this.getFirstName() + this.getMiddleName() +  this.getLastName();
    }
 }

Tutaj szczegóły implementacji pełnej nazwy są ukryte przed użytkownikiem i nie są dostępne bezpośrednio dla użytkownika, w przeciwieństwie do atrybutu publicznego.


1
Dla mnie posiadanie ton pobierających i ustawiających, którzy nie robią nic wyjątkowego, jest bezużyteczne. getFullName jest wyjątkiem, ponieważ robi coś innego. Mając tylko trzy zmienne publiczne, a następnie utrzymanie getFullName sprawi, że program będzie łatwiejszy do odczytania, ale nadal będzie ukryta ta pełna nazwa. Generalnie nie mam nic przeciwko getterom i seterom, jeśli a. robią coś wyjątkowego i / lub b. masz tylko jeden, tak, możesz mieć publiczny finał i tak dalej, ale nie
FacelessTiger

1
Zaletą tego jest to, że możesz zmienić elementy wewnętrzne klasy bez zmiany interfejsu. Powiedzmy, że zamiast trzech właściwości miałeś jedną - tablicę ciągów. Jeśli korzystasz z programów pobierających i ustawiających, możesz wprowadzić tę zmianę, a następnie zaktualizować moduł pobierający / ustawiający, aby wiedzieć, że imiona [0] to imię, imiona [1] są środkowe itp. Ale jeśli właśnie użyłeś właściwości publicznych , musisz również zmienić każdą klasę Pracownik, do którego uzyskano dostęp, ponieważ właściwość firstName, której używali, już nie istnieje.
Andrew Hows,

1
@AndrewHows z tego, co widziałem w prawdziwym życiu, kiedy ludzie zmieniają elementy wewnętrzne klasy, zmieniają również interfejs i dokonują dużego refaktoryzacji całego kodu
Eildosa

2

Innym zastosowaniem (w językach obsługujących właściwości) jest to, że setery i gettery mogą sugerować, że operacja nie jest trywialna. Zazwyczaj chcesz uniknąć robienia czegokolwiek kosztownego obliczeniowo we właściwości.


Nigdy nie spodziewałbym się, że getter lub setter będzie kosztowną operacją. W takich przypadkach lepiej wykorzystać fabrykę: użyć wszystkich ustawiające trzeba i wtedy uruchamiamy drogie executelub buildmetody.
Hubert Grzeskowiak

2

Jedną stosunkowo nowoczesną zaletą programów pobierających / ustawiających jest to, że ułatwia przeglądanie kodu w edytorach tagowanych (indeksowanych). Np. Jeśli chcesz zobaczyć, kto ustawia członka, możesz otworzyć hierarchię połączeń setera.

Z drugiej strony, jeśli członek jest publiczny, narzędzia nie umożliwiają filtrowania dostępu do odczytu / zapisu do członka. Musisz więc przebrnąć przez wszystkie zastosowania członka.


możesz zrobić poprawne kliknięcie> znaleźć użycie na elemencie dokładnie tak, jak na getter / seter
Eildosa

2

W języku obiektowym metody i ich modyfikatory dostępu deklarują interfejs dla tego obiektu. Pomiędzy konstruktorem a metodami akcesora i mutatora deweloper może kontrolować dostęp do wewnętrznego stanu obiektu. Jeśli zmienne są po prostu zadeklarowane jako publiczne, nie ma sposobu na regulację tego dostępu. A kiedy używamy seterów, możemy ograniczyć użytkownika do potrzebnych nam danych wejściowych. Oznacza to, że feed dla tej bardzo zmiennej będzie przechodził przez właściwy kanał, a kanał jest przez nas predefiniowany. Więc bezpieczniej jest używać seterów.


1

Dodatkowo ma to na celu „zabezpieczenie przyszłości” twojej klasy. W szczególności zmiana z pola na właściwość jest przerwą ABI, więc jeśli później zdecydujesz, że potrzebujesz więcej logiki niż tylko „ustaw / pobierz pole”, to musisz przerwać ABI, co oczywiście stwarza problemy dla czegokolwiek jeszcze skompilowane przeciwko twojej klasie.


2
Przypuszczam, że zmiana zachowania gettera lub setera nie jest wtedy przełomową zmianą. </sarcasm>
R. Martinho Fernandes

@ R.MartinhoFernandes nie zawsze musi być. TBYS
Phil

1

Chciałbym po prostu rzucić pomysł na adnotacje: @getter i @setter. Z @getter powinieneś mieć możliwość obj = class.field, ale nie class.field = obj. Z @setter i odwrotnie. Z @getter i @setter powinieneś być w stanie zrobić obie rzeczy. Pozwoliłoby to zachować enkapsulację i skrócić czas, nie wywołując prostych metod w czasie wykonywania.


1
Zostałby zaimplementowany w czasie wykonywania przy użyciu „trywialnych metod”. Właściwie prawdopodobnie nietrywialne.
Phil

Dziś można to zrobić za pomocą preprocesora adnotacji.
Thorbjørn Ravn Andersen
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.