Zwracanie obiektu w akcesorze a hermetyzacja

Zwracanie obiektu w akcesorze a hermetyzacja
K1
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:2
0

W jaki sposób mam skonstruować metodę akcesora getTrzymaneKarty(), żeby nie złamać zasady hermetyzacji?
Mam klasę Uzytkownik z polem: private ArrayList<Karta> trzymaneKarty = new ArrayList<Karta>(13); Mam też oczywiście klasę Karta. Jeśli w klasie Uzytkownik zrobię metodę

Kopiuj
public ArrayList<Karta> getTrzymaneKarty() {
		return trzymaneKarty;
	}

to wystarczy w dowolnej klasie napisać nazwaUzytkownika.getTrzymaneKarty().add(new Karta(3,3)); aby zmienić prywatne pole w klasie Uzytkownik. Pomyślałem, że trzeba zwrócić kopię, ale oczywiście

Kopiuj
public ArrayList<Karta> getTrzymaneKarty() {
		kopia = trzymaneKarty;
		return kopia;
	}

taki zapis nic nie da, bo kopia odwoła się do tego samego obiektu i zmieniając kopię jego też zmienimy. Wobec tego pomyślałem, żeby przepisać całą listę do nowej listy i zwrocić tą nową.

Kopiuj
ArrayList<Karta> kopia = new ArrayList<Karta>(13);
kopia.addAll(trzymaneKarty);
return kopia;

Jednak nie wiem czemu nagle pojawiać się zaczał błąd przy linii if (temp.get(j).getLiczbaNaKarcie() == temp.get(k).getLiczbaNaKarcie())
"Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 10, Size: 10
at java.util.ArrayList.rangeCheck(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at Glowna.wyrzucPary(Glowna.java:85)
at Glowna.main(Glowna.java:22)"

w zasadzie o to też chciałbym zapytać dlaczego przy normalnym 'return trzymaneKarty' błędu nie było, a w tej wersji z kopią się pojawił. Mogę przekopiować cały kod, tylko nie wiem czy to konieczne.

Wracając do głównego problemu. Gdy zacząłem się zastanawiać nad powyższym błędem z kopią, dotarło do mnie, że w ten sposób zwrocę rzeczywiście inną listę i zmiana jej nie zmieni tej prywatnej listy z klasy, ale zwróce w niej te same obiekty klasy Karta co w oryginalnej liście! Wtedy nawet gdyby poprawnie działała wersja z przepisaniem listy do kopii, zabezpieczyłbym się chyba przed
nazwaUzytkownika.getTrzymaneKarty().add(new Karta(3,3));
bo nie dodałoby to już tej karty do trzymanych kart użytkownika. Wystarczy jednak napisać
nazwaUzytkownika.getTrzymaneKarty().get(1).setId(4);
i zmienimy obiekt Karta posiadany przez danego użytkownika w swojej liście trzymaneKarty.

Nie wiem czy zacząłem przesadzać, bo wygląda to dość śmiesznie, kolejno get.get.set na jednym obiekcie. Można zostawiać taką furtkę? Według mnie nie powinno tak być. Jak to rozwiązać, żeby rzeczywiście mieć hermetyczny akcesor? Ewentualnie poproszę o inne rozwiązanie. Widzę, że jest metoda clone(), ale tu sprawa wygląda jak z przepisywaniem listy. Klonuje listę, ale w niej będą referencje do tych samych obiektów klasy Karta. Znalazłem jakieś przykłady z przesłanianiem metody clone(), żeby uzyskać klonowanie głębokie, ale nie do końca się w tym łapie. Do tego w głowie rodzi mi się "a co jeśli Karta też miałaby w sobie referencje do innych obiektów". Przecież potem się w tym nie da połapać.
Kolejna rzecz, którą napotkałem to refleksje. To już dla mnie coś chyba nie do ogarnięcia na ten moment. W głowie już mi się miesza. Doradźcie proszę jak do tego podejść. Napisałbym więcej moich wątpliwości i pomysłów, ale na razie poczekam na odpowiedź, bo nikomu nie będzie się chciało takiego długiego posta czytać.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0
  1. Przesadziłeś i tyle. Jesteśmy dorosłymi ludźmi. Jak ktoś będzie chciał namieszać w bebechach danej klasy to i tak sobie poradzi. Powinieneś zwracać normalnie tą kolekcje co masz. Enkapsulacja i hermetyzacja służą między innymi do tego że udostępniasz użytkownikowi tylko interfejs, a nie informacje o wewnętrznej budowie obiektów. Zauważ że dzięki takiemu akcesorowi getTrzymaneKarty() user wcale nie wie co to za kolekcja którą dostaje! Przecież ty ją możesz tam w locie konstruować i mu zwracać na bazie jakichś innych obiektów. Albo możesz robić głęboką kopię właśnie. Użytkownik tego nie wie, dlatego normalny użytkownik nie będzie polegał na efektach ubocznych ;]
  2. A co jest dziwnego w refleksji? Ot jest to po prostu mechanizm pozwalający manipulować metadanymi obiektów (np. zmieniać modyfikatory dostępu, pobierać listę metod itd) i dobierać sie do bebechów obiektów. W praktyce refleksja jest stosowana tylko w bardzo szczególnych przypadkach (np. tworzenie w runtime obiektów które nie były znane w chwili pisania kodu). Jeśli musisz gdzies jej użyć to na 99% bardzo źle coś zaprojektowałeś.

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
0

W Javie nie ma głębokiego consta takiego jak w C/ C++, ale nawet ten const z C/ C++ nie da w 100% zabezpieczenia bez modyfikacji co bardziej skomplikowanych struktur danych (tzn dokładnie wskaźniki muszą być oconstowane, bo nie 'dziedziczą' consta).

Jest funkcja java.util.Collections.unmodifiableList, która działa jak płytki const, tzn zabezpiecza przed dodawaniem, podmienianiem, usuwaniem, etc referencji bezpośrednio w liście, no ale sekwencje typu:

Kopiuj
lista.get(x).setCośtam()

nadal będą działać, bo ta unmodifiableList nie modyfikuje zwracanych obiektów, w szczególności nie zabezpiecza ich przed modyfikacją.

A co do kodu:

Kopiuj
ArrayList<Karta> kopia = new ArrayList<Karta>(13);
kopia.addAll(trzymaneKarty);
return kopia;

Możesz go skrócić do:

Kopiuj
return new ArrayList<Karta>(trzymaneKarty);

o ile trzymaneKarty jest kolekcją.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
K1
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 11 lat
  • Postów:2
0
Shalom napisał(a):

W praktyce refleksja jest stosowana tylko w bardzo szczególnych przypadkach (np. tworzenie w runtime obiektów które nie były znane w chwili pisania kodu). Jeśli musisz gdzies jej użyć to na 99% bardzo źle coś zaprojektowałeś.
Raczej nie muszę, to nie jest mocno skomplikowany projekt. Po prostu gdzieś znalazłem, że może to być rozwiązanie podobnego problemu.

Wibowit napisał(a):

o ile trzymaneKarty jest kolekcją.
tak, to jest ten sam typ ArrayList<Karta>

Rozumiem, że wywołania nazwaUzytkownika.getTrzymaneKarty().get(1).setId(4); są nienaturalne i można tak nie robić, ale wystarczy, że ja sam się zapomnę, albo wrócę do tego za jakiś czas, ewentualnie ktoś inny będzie z tego korzystał i już się robi nieciekawie, bo można dać gdzieś w programie

Kopiuj
ArrayList<Karta> karty = jakisUzytkownik.getTrzymaneKarty();
//jakieś operacje wykonywane na karty
innyUzytkownik.setTrzymaneKarty(karty); 

Mając zamiar przekopiowania kart od użytkownika i (po pewnych modyfikacjach) wstawieniu ich nowemu, nie będziemy oczekiwać zmiany kart tego pierwszego użytkownika. Wobec tego chyba nie mogę tego tak zostawić.

Nie ma jakiegoś prostego sposobu na to? Znalazłem jakiś przykład z głębokim kopiowaniem za pomocą przesłaniania metody clone() i chyba go rozumiem, ale czy to najlepszy sposób? Czy każdą klasę trzeba w ten sposób zabezpieczać z myślą, że może jakaś inna będzie miała kolekcję z ich obiektami i dane wypłyną?

Jeszcze ten drugi problem, dlaczego w metodzie getTrzymaneKarty() to return new ArrayList<Karta>(trzymaneKarty);oraz to return trzymaneKarty nie wyrzuca mi żadnych błędów, a ta moja wersja

Kopiuj
ArrayList<Karta> kopia = new ArrayList<Karta>(13);
kopia.addAll(trzymaneKarty);
return kopia; 

wyrzuca IndexOutOfBoundsException w innej metodzie? Jak to w ogóle możliwe? Skoro lista.get(index) nie wykracza poza zakres listy, to dlaczego nagle na kopii zaczął wykraczać?

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 13 godzin
0

A nie robisz przypadkiem w kodzie czegoś typu:

Kopiuj
for (int i = 0; i < trzymaneKarty.size(); i++) {
  kopia.get(i);
}

?


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Mając zamiar przekopiowania kart od użytkownika i (po pewnych modyfikacjach) wstawieniu ich nowemu, nie będziemy oczekiwać zmiany kart tego pierwszego użytkownika. Wobec tego chyba nie mogę tego tak zostawić.

No dobra, ale ty tam nic nie kopiujesz. Ty po prostu albo zabierasz karty jednemu i dajesz drugiemu, albo mówisz im "to są wasze wspólne karty". Jeśli każdy ma mieć swoje karty to MUSISZ stworzyć nowe karty (na przykład klonując).

Czy każdą klasę trzeba w ten sposób zabezpieczać z myślą, że może jakaś inna będzie miała kolekcję z ich obiektami i dane wypłyną?

Nie i nikt normalny tak nie robi bo nie ma potrzeby. Tak jak mówiłem, bądźmy dorośli i poważni.

A jak już tak bardzo chcesz te swoje karty zabezpieczyć to jest prosta recepta: napisz klasę TaliaKart która przechowuje karty (np. w List) i udostępnia tylko taki interfejs jaki chcesz. W efekcie user nawet jeśli zrobi getTrzymaneKarty() to nie dostanie List<T> tylko TaliaKart. Ogólnie to jest dobry pomysł i nazywa się Zasadą Separacji Interfejsów. Każdy obiekt powinien posiadać minimalny interfejs potrzebny do swojego działania.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)