Kiedy public? Kiedy private?

2

Motyw dosyć mocno programistyczny, ale nie mogłem się zdecydować do jakiego działu wrzucić - raczej nie jest to temat nietuzinkowy, więc jest tutaj :P.

Zostałem nauczony żeby wszystkie (jak najwięcej się da) pola klas ustawiać jako private, i udostępniać odpowiednie metody. Jest to zgodne z pewnymi kanonami programowania obiektowego - klasa powinna być czarną skrzynką wykonującą pewne zadanie, jej pola nie powinny nas za wiele obchodzić (podczas korzystania z niej), a wszystkie interakcje z klasą powinny się odbywać za pomocą jej metod.
Ostatnio jednak coraz częściej zadaję sobie pytanie czy ta cała konwencja faktycznie ma sens ? Coraz częściej mam wrażenie, że piszę tuziny metod, które de facto umożliwiają mi tylko swobodny dostęp do składowej jakiejś klasy - czyli robią to samo co modyfikator public.

Dzisiaj znowu stanąłem przed dylematem public czy private ? Wpisałem frazę w google i znalazłem na pewnym forum kogoś kto pytał o to samo. Przeważającą ilość odpowiedzi można by streścić w nakazie: "Używaj privat, chyba, że już naprawdę nie masz innego wyboru". Naprawdę ? Czy modyfikator public to jakieś prastare zło ?

Przemyślałem sytuację odnośnie mojego kodu i postanowiłem:

  1. Ustawię public. Sorry, ale wydaje mi się że naprawdę nie ma sensu na siłę ustawiać jakiegoś pola np. String jako private, tylko po to, żeby musieć potem dodać metody getString() i setString(String str).
  2. Napiszę o tym tutaj, być może ktoś poda jakieś sensowne powody wciskania wszędzie private, lub przychyli się do moich wniosków.
0

Interfejs powinien byc publiczny, wszystko inne jak najbardziej "sprywatyzowane". Wyjatkiem od tej reguly sa klasy matematyczne, gdzie skladowe rowniez powinny byc publiczne.

Dlaczego tak sie robi?

  1. Ograniczamy mozliwosci uzytkownika co do manipulowania wartosciami obiektow skladowych.
  2. Udostepniamy jasno okreslone ramy interfejsu, z ktorego uzytkownik moze korzystac.
  3. Zostawiamy sobie furtke na zmiane ew. zmiane implementacji bez wymagania od uzytkownika zmian w kodzie.
1

jeżeli i tak od razu dajesz settery i gettery do zmiennej i nie jest potrzebna walidacja danych to można spokojnie dać public.

Każdy będzie miał swoje zdanie na ten temat, a w pracy dostaniesz wytyczne jak masz pisać i będziesz się musiał tego trzymać.

0

Dylemat można rozbić na dwa przypadki:

  1. Pisanie DTO lub tego typu rzeczy - tutaj zwykle występują magiczne operacje na bajtkodzie, np jakieś aspekty lub magia Hibernate. Bez getterów i setterów magia nie działa.
  2. Pisanie klas które rzeczywiście coś robią - tutaj zgodnie z OOP należy zenkapsulować stan i najlepiej go w ogóle nie udostępniąc, a metody powinny robić coś konkretnego, a nie służyć tylko i wyłącznie do łamania enkapsulacji.

Oczywiście są sytuacje w których robienie getterów i setterów ma mało sensu. Równocześnie jest wiele przypadków w których można użyć Project Lombok i zaoszczędzić klepania kodu bez rezygnacji z (pseudo) enkapsulacji i możliwości rozszerzania logiki dostępowej.

0

Przede wszystkim, w ten sposób uodparniasz kod klienta na przyszłe zmiany w API. Przykładowo w przyszłości możesz zadecydować, że nie będziesz już pobierać danych z pola w tej klasie, tylko twoja klasa będzie stanowić jakby proxy do innej (jak powstanie nowy model). Możesz też dzięki temu decydować kto ma prawo modyfikować dane pola, a na zewnątrz dać tylko publiczny getter. Przydatne jest to też w testach jednostkowych jak chcesz zamockować daną klasę i np. zweryfikować czy został w danym momencie wywołany setter.
Poza tym jedną z największych korzyści czynienia pól jako private jest możliwość synchronizacji stanu obiektu, co prawda idealnie jest kiedy obiekt jest immutable (wtedy mogą być wszystkie pola publiczne i stałe) ale nie zawsze się da i zazwyczaj trzeba kontrolować dostęp do pól.

Oczywiście nie zawsze robienie pól jako private ma sens, np. w wewnętrznym API, a przynajmniej w prywatnych wewnętrznych strukturach zazwyczaj to nie ma żadnego celu więc możesz sobie odpuścić. Przy bardzo prostych strukturach typu para klucz-wartość lub wektorach typu punkt też można zrobić wszystkie pola jako public final, ale np. ja preferuję mimo wszystko użyć getterów i setterów, już tak z przyzwyczajenia :).

0

Nie wiem czy podal jaki jezyk, ale np. sa takie w ktorych dostep do pol czy tez propertisow jest taki sam (obiekt.pole) i nie ma rozdzielenia na settery gettery i dostep do pola. Niektore wymagaja rekompilacji poniewaz generowany kod jest inny (np. zdaje sie Scala, @CompileStatic Groovy); inne nie wymagaja (dynamiczny Groovy - sama refleksja ktora sprawdza i gettery i pola, przez co moze byc roznica w wydajnosci - czy Python). Wszystko zalezy. Przyklad: Python nie ma wcale czegos takiego jak modyfikatory dostepu, tylko pewne konwencje (name mangling jesli prefiksem jest __ sie nie liczy bo nie chroni przed niczym) i nikt sie nie zamartwia i jest ok. A mozliwosci jakie daje mozna poogladac np. w kodzie Mercuriala, gdzie rozszerzenia potrafia zmieniac dzialanie wbudowanych komend.
Ja osobiscie jestem przeciwko pisania setterow getterow na potege (wstrzykiwanie wole za pomoca konstruktora) i unikam jak ognia, jednakze obecnie w firmie konwencje i kod sa takie, ze bez tego sie nie da i juz, wiec pisze je bo po prostu nie chce mi sie klocic z kazdym kolejnym javowcem o to czy to ma sens czy nie. (Javowcow uwazam ogolnie za ludzi uposledzonych programistycznie i bardzo ograniczonych - nie wszyscy, taki Wibowit czy inny Bogdans sa wyjatkami, ale znaczna wiekszosc to code-monkeys. To pisze ja, programista Javy <glownie> ;d).

3

z doswiadczenia, NIGDY nie uzywaj publicznych atrybutow klasy

0

Dzięki za rozwinięcie tematu,

n0name_l - rozumiem, że przez "użytkownik" masz na myśli innego programistę pracującego z moim kodem ? Bo przecież użytkownik końcowy i tak dostaje gotowy produkt i nie wprowadzi żadnych zmian w kodzie ?

fasadin - mam podobne zdanie.

Wibowit, axxxxx - czyli koniec końców tak jak we wszystkim - nie ma złotego środka i należy się dostosować do konkretnej sytuacji.

mućka - też piszę w Javie :P Choć tak naprawdę to jeszcze jestem noobem i dopiero pomału wykształcam własne nawyki i idee programowania.

cepa -

z doświadczenia, NIGDY nie używaj publicznych atrybutów klasy

  • potrafię zrozumieć, że jeżeli mam narzucone przez kogoś wytyczne, że wszystkie pola mają być private to mam się tego trzymać, ale nie rozumiem dlaczego sam z własnej woli mam na siebie nakładać takie, czasem bezsensowne ograniczenie ? Dzisiaj np. pisałem pewną klasę, której 8 na 9 pól będę później wyświetlał - czyli potrzebuję gettery, i które to pola użytkownik będzie mógł edytować - czyli settery, więc po co mam robić metodę typu edit(lista parametrów) a wewnątrz get i set skoro mogę ustawić pola do edycji jako public ? nie przeszkodzi mi to wcale w walidacji danych. Sprawdzę poprawność parametrów a następnie szybko, łatwo i bezpośrednio ustawię wartości zamiast wywoływać dodatkowe metody, które zrobią to samo
0

@cepa - pytanie po co mockowac, przeciazac, lazy-loadowac skladowe klasy Point. Nie wiem co to za klasa, ale brzmi calkiem prosto - x i y (jesli to 2d). Nie wiem po co tutaj lazy loading. Nie wiem co chcesz mockowac skoro mozna new Point(0, 0), przeciazac to mozesz metody biznesowe ale nie settery.
Bylo mowione ze walidacja itp. nie zawsze sa potrzebne, np. w klasie Point jestem sklonny stwierdzic ze nie jest - wszak dowolne wartosci x i y sa poprawne? Jesli czasami ma byc walidacja a czasami nie, to juz jakas inna klasa sie powinna tym zajmowac, ta ktora tworzy instancje dla opowiedniego kontekstu.

0
Qbisiek napisał(a):

cepa -

z doświadczenia, NIGDY nie używaj publicznych atrybutów klasy

  • potrafię zrozumieć, że jeżeli mam narzucone przez kogoś wytyczne, że wszystkie pola mają być private to mam się tego trzymać, ale nie rozumiem dlaczego sam z własnej woli mam na siebie nakładać takie, czasem bezsensowne ograniczenie ? Dzisiaj np. pisałem pewną klasę, której 8 na 9 pól będę później wyświetlał - czyli potrzebuję gettery, i które to pola użytkownik będzie mógł edytować - czyli settery, więc po co mam robić metodę typu edit(lista parametrów) a wewnątrz get i set skoro mogę ustawić pola do edycji jako public ? nie przeszkodzi mi to wcale w walidacji danych. Sprawdzę poprawność parametrów a następnie szybko, łatwo i bezpośrednio ustawię wartości zamiast wywoływać dodatkowe metody, które zrobią to samo
mućka napisał(a):

@cepa - pytanie po co mockowac, przeciazac, lazy-loadowac skladowe klasy Point. Nie wiem co to za klasa, ale brzmi calkiem prosto - x i y (jesli to 2d). Nie wiem po co tutaj lazy loading. Nie wiem co chcesz mockowac skoro mozna new Point(0, 0), przeciazac to mozesz metody biznesowe ale nie settery.
Bylo mowione ze walidacja itp. nie zawsze sa potrzebne, np. w klasie Point jestem sklonny stwierdzic ze nie jest - wszak dowolne wartosci x i y sa poprawne? Jesli czasami ma byc walidacja a czasami nie, to juz jakas inna klasa sie powinna tym zajmowac, ta ktora tworzy instancje dla opowiedniego kontekstu.

Jest taka stara dobra zasada "ch0jowo ale jednakowo", Point to akurat trywialna klasa, ALE jak sie trzymamy konwencji w kodzie to sie trzymamy. Settery i gettery nie sa tylko po to aby dobrac sie do danych obiektu, ALE do tego, aby zapewnić stabilność interfejsu w długim projekcie.
Po co?
Ano po to, że np: chce zrobić interfejs PointInterface czy tam IPoint ktory udostepnia dane o N-wymiarowej przestrzeni, sposob implementacji mnie nie interesuje ale chce mieć metody typu: setCoord(i,v), getCoord(i), setCoords(v), getCoords() i co mam zrobic public x, public y? A co jak za rok implementacja Point2D i Point3D przejdzie na vektory lub cokolwiek innego?
Po co mockowac?
Aby nie uzależniać się od konkretnej implementacji -> programowanie na interfejsach.
Po co przeciążać?
Teoretyczny przypadek typu punktu w cwiartce (0,oo+) (0,oo+), mam walidować poza klasą Point? To doprowadzi do duplikacji kodu, miejscem walidacji parametrow obiektu jest obiekt, bo to jego domena, wieć dlatego wole mieć settery i gettery ktore moge przeciązać.

0

Załóżmy, że masz dwie zmienne: X i Y. Dalej, załóżmy że klasa ma pozwalać na pobieranie wartości tych zmiennych, ale na ustawianie tylko zmiennej X - jeśli X < 0, ustaw Y na -1, X==0 -> Y = 0, X > 0 -> Y = 1.

W takim wypadku, nie możesz zrobić zmiennej public y (która "zastępuje" pakiecik setY i getY), ponieważ chcesz zrobić tylko getY.
Dalej, nie możesz zrobić zmiennej public x, ponieważ o ile getX będzie po prostu zwracał wartość X, o tyle jednak w przypadku setX chcesz nie tylko ustawić X, ale również zmienić na jego podstawie wartość Y.

Przykład:
setX(5)
ustala od razu X na 5 i Y na 1.

x = 5;
y = 1;

Okej, to też zadziała. Ale wyobraźmy sobie, że zapomniałeś o Y:
x = -5;
Y nadal równe jest 1.

0

W językach funkcyjnych namiętnie używa się krotek, czyli klas zawierających kilka pól, zero specyficznej logiki i nawet brak specyficznych nazw pól.

Dla Javy są np tutaj: http://www.javatuples.org/ aczkolwiek w Javie i tak brakuje cywilizowanej składni do obsługi krotek - w przeciwieństwie do Scali czy Pythona, gdzie krotki wyglądają dość ładnie (ale i tak nie lubię Pythona).

Krotki można oczywiście nadużyć i wprowadzić więcej szkody niż pożytku. Nadają się głównie do super-zlokalizowanych typów, np przy zwracaniu pewnej kombinacji typów obiektów, która to kombinacja jest unikalna i nigdzie indziej nie używana.

A Scala ma tyle składniowych bajerów, że tam są zupełnie inne dylematy ;]

0

Modyfikatora public w stosunku do pól powinno się używać jedynie w przypadku gdy używasz przy okazji final. W takim przypadku nie ma potrzeby tworzenia dodatkowego gettera, a setter nie ma sensu.
Hermetyzacja za pomocą getterów i setterów nie ma sensu > http://koziolekweb.pl/2012/01/20/ekstremalna-obiektowosc-w-praktyce-czesc-9-nie-uzywaj-getterowsetterowwlasnosci/

W ogólności:

  • obiekty danych np. Point, Pracownik itp. powinny być niezmienne. Ważne szczególnie w środowisku wielowątkowym. W przypadku użycia ORMa można dodatkowo użyć narzędzi kontroli wersji dla encji(przykładowo http://www.jboss.org/envers).
  • obiekty "biznesowe" nie powinny mieć setterów/getterów w ogóle, a jedynie udostępniać metody biznesowe. Wiązanie zależności za pomocą DI.
0

@Koziołek - pisalem cos podobnego rano, ale wyszlo mi duzo dluzsze i doszedlem do wniosku, ze nie wysle bo i tak cepa obali to jakims tekstem o cyckach albo czyms. Stwierdzilem ze nie ma sensu. Ale musze powiedziec, ze po raz pierwszy podpisuje sie pod tym co napisales bez zastrzezen ;d

2

Ja jestem zdania że setterów i geterów trzeba unikać tak samo jak robienia zmiennych klasy jako public. Obiekt nie powinien udostępniać swojej implementacji na zewnątrz. To obiekt powinien udostępniać niezbędne metody do pracy na nim. Przykładowo, możemy mieć obiekt Enemy i w nim pole prywatne alive, i teraz można stworzyć metody setAlive i isAlive i zarządzać w ten sposób życiem obiektu, tylko po co? Nie lepiej w takim przypadku :

  1. w konstruktorze ustawiać alive na true
  2. udostępnić metode kill() która ustawi alive na false i np pchnie notyfikacje do obserwatorów, jeden z obserwatorów, który zarządza listą obietków dowie sie ze obiekt zginął poprzez notyfikacje.
0

@spartanPAGE, tyle tylko, że gettery/settery nie powinny zawierać logiki ponad walidację danych, a i to można np. w javie załatwić za pomocą beanvalidation albo aspektów.

2

W Javie wybór między public a private itd. to nie tylko kwestia dobrego designu ale też kwestia bezpieczeństwa jeżeli używa się sandboxa. Jeżeli z uprzywilejowanego kodu wycieknie referencja do ważnego obiektu bo ktoś dał protected zamiast private to mogą stać się złe rzeczy :P

0

Ok, ale wiekszosc Javowego kodu jaki ja niestety widze to jest kombos: pole private i getter i setter. I jeszcze nosi to dumna nazwe 'wzorca' czy tez 'idiomu'. Ktos kto zajmuje sie takim kodem wie ze pole musi byc private.
Jak wspomniano, i sam o tym wiesz lepiej niz ja, sa jezyki ktore nie maja private, i jakos daja rade. Sandbox sandboxem, referencje private, a i tak sa wektory ataku np. dzieki polaczenia nowych MethodHandles, Toolkit.getDefaultTookit() i jmx (czy jakos tak, dokladnie nie pamietam) ;d

0

@mućka, to jest to o czym pisałem na blogu - specyfikacja JavaBeans, w pewnym sensie, promuje takie właśnie niedobre praktyki. Z drugiej strony mało kto tworzy klasy wyposażone w całą baterię propertyChangeLIstenerów "bo po co", dla których wymyślono w praktyce g/s jako metody.
Inna sprawa, że przez pewien dłuższy okres popularne narzędzia jak Hibernate czy Spring bez g/s słabo działały.

0

Dlatego tez chyba w ktoryms z poprzednich postow wspomnialem o spring (a moze to byl ten ktorego w koncu nie wyslalem?).
Ale pytanie jest takie: co mnie to obchodzi, ze jakas biblioteka czegos nie robi? Czy to znaczy ze mam tak robic wszedzie w kodzie, jak Cepa (c**** ale jednakowo to konformizm)? Poza tym, JavaBeans zostaly wymyslone po to zeby graficzne edytory mialy ustalony protokol do wyswietlania wlasciwosci obiektow ktore leza gdzies na formie; dopiero pozniej jakies madre glowy wymyslily ze POJO == JavaBeans i w sumie fajnie transakcyje uslugi itp. rowniez zrobic mutowalne za pomoca setterow...
Ogolnie to w HB rozumiem, bo najczesciej takie zmapowane obiekty to elementy tzw. anemicznego modelu domenowego, ktory jest ladnym okresleniem na fakt, ze domenowe obiekty to nic wiecej tylko worki na dane. W Springu wole wstrzykiwanie do konstruktora, ale spring radzi sobie zdaje sie rowniez z prywatnymi setterami? Wzglednie package private, zeby bylo latwo testowac (ale i tak wole konstruktory!).

1 użytkowników online, w tym zalogowanych: 0, gości: 1