Szczególnie w Javie, ale prawdopodobnie także w innych językach: kiedy przydatne byłyby dwa odniesienia do tego samego obiektu?
Przykład:
Dog a = new Dog();
Dob b = a;
Czy istnieje sytuacja, w której byłoby to przydatne? Dlaczego miałoby to być preferowanym rozwiązaniem, a
jeśli chcesz korzystać z obiektu reprezentowanego przez a
?
object-oriented
object-oriented-design
object
Bassinator
źródło
źródło
a
ib
zawsze odnoszą się do tego samegoDog
, to nie ma sensu. Jeśli czasem mogą, to jest to całkiem przydatne.Odpowiedzi:
Przykładem może być ten sam obiekt na dwóch osobnych listach :
Innym zastosowaniem jest użycie tego samego obiektu w kilku rolach :
źródło
Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;
- gdzie masz wiele możliwych wartości (lub listę dostępnych wartości) i odniesienie do aktualnie aktywnej / wybranej.currentPersona
wskazuje na jeden przedmiot lub drugi, ale nigdy oba. W szczególności może być łatwo możliwe, że nigdy niecurrentPersona
jest ustawione , w takim przypadku z pewnością nie jest to odniesienie do dwóch obiektów. Powiedziałbym, że w moim przykładzie, zarówno i są odniesienia do tej samej instancji, lecz służą różne znaczenie semantyczne w ramach programu.bruce
batman
currentPersona
To właściwie zaskakująco głębokie pytanie! Doświadczenie ze współczesnego C ++ (i języków zaczerpniętych ze współczesnego C ++, takich jak Rust) sugeruje, że bardzo często tego nie chcesz! W przypadku większości danych potrzebujesz pojedynczego lub unikalnego odwołania („będącego właścicielem”). Pomysł ten jest również jednym z głównych powodów stosowania systemów liniowych .
Jednak nawet wtedy zwykle potrzebujesz krótkich „pożyczonych” referencji, które są używane do szybkiego dostępu do pamięci, ale nie trwają przez znaczną część czasu, w którym dane istnieją. Najczęściej, gdy przekazujesz obiekt jako argument do innej funkcji (parametry też są zmienne!):
Rzadszy przypadek, gdy używasz jednego z dwóch obiektów w zależności od warunku, robiąc zasadniczo to samo, niezależnie od tego, który wybierzesz:
Wracając do bardziej powszechnych zastosowań, ale pozostawiając lokalne zmienne, zwracamy się do pól: Na przykład widżety w ramach GUI często mają widżety nadrzędne, więc twoja duża ramka zawierająca dziesięć przycisków miałaby co najmniej dziesięć odnośników wskazujących na nią (plus kilka innych od jego rodzica i być może od słuchaczy wydarzeń i tak dalej). Każdy rodzaj wykresu obiektowego i niektóre rodzaje drzew obiektowych (te z odniesieniami do rodzica / rodzeństwa) mają wiele obiektów odnoszących się do każdego tego samego obiektu. I praktycznie każdy zestaw danych to tak naprawdę wykres ;-)
źródło
Zmienne tymczasowe: rozważ następujący pseudokod.
Zmienne
max
icandidate
mogą wskazywać na ten sam obiekt, ale przypisanie zmiennych zmienia się przy użyciu różnych reguł i w różnym czasie.źródło
Aby uzupełnić inne odpowiedzi, możesz również przejrzeć strukturę danych inaczej, zaczynając od tego samego miejsca. Na przykład, jeśli masz
BinaryTree a = new BinaryTree(...); BinaryTree b = a
, możesz przejść w dół do lewej ścieżki drzewaa
i jej prawej ścieżkib
, używając czegoś takiego:Minęło trochę czasu, odkąd napisałem Javę, więc kod może być niepoprawny lub rozsądny. Weź to bardziej jako pseudokod.
źródło
Ta metoda jest świetna, gdy masz kilka obiektów, które wszystkie oddzwaniają do innego obiektu, z którego można korzystać niekoniecznie.
Na przykład, jeśli masz interfejs z kartami, możesz mieć Tab1, Tab2 i Tab3. Możesz także chcieć używać wspólnej zmiennej, niezależnie od tego, na której karcie użytkownik jest włączony, aby uprościć kod i zmniejszyć konieczność ciągłego zastanawiania się nad tym, na której karcie znajduje się użytkownik.
Następnie na każdej z ponumerowanych kart naClick można zmienić CurrentTab, aby odwoływać się do tej karty.
Teraz w swoim kodzie możesz bezkarnie wywoływać „CurrentTab”, nie musząc wiedzieć, na której karcie się aktualnie znajdujesz. Możesz także zaktualizować właściwości CurrentTab, a one automatycznie spłyną do odnośnej karty.
źródło
Istnieje wiele scenariuszy, w których
b
musi być odniesienie do nieznanego „a
”, aby było przydatne. W szczególności:b
wskazuje na czas kompilacji.Na przykład:
Parametry
t
jest odniesieniem do zmiennej z zakresu zewnętrznego.Zwracane wartości i inne wartości warunkowe
biggerThing
iz
każdy z nich jest odniesieniem do jednegoa
lubb
. Nie wiemy, które w czasie kompilacji.Lambdas i ich wartości zwracane
x
jest parametrem (przykład 1 powyżej) it
jest wartością zwracaną (przykład 2 powyżej)Kolekcje
index["some name"]
jest w dużej mierze bardziej wyrafinowanyb
. Jest to alias do obiektu, który został utworzony i umieszczony w kolekcji.Pętle
t
jest odwołaniem do elementu zwróconego (przykład 2) przez iterator (poprzedni przykład).źródło
Jest to kluczowa kwestia, ale IMHO jest warte zrozumienia.
Wszystkie języki OO zawsze wykonują kopie referencji i nigdy nie kopiują obiektu „niewidocznie”. O wiele trudniej byłoby pisać programy, gdyby języki OO działały w jakikolwiek inny sposób. Na przykład funkcje i metody nigdy nie mogłyby zaktualizować obiektu. Java i większość języków OO byłyby prawie niemożliwe do użycia bez znacznej dodatkowej złożoności.
Obiekt w programie powinien mieć jakieś znaczenie. Na przykład reprezentuje coś konkretnego w prawdziwym świecie fizycznym. Zwykle wiele odniesień do tej samej rzeczy ma sens. Na przykład mój adres domowy można podać wielu osobom i organizacjom, a adres ten zawsze odnosi się do tej samej fizycznej lokalizacji. Pierwszą kwestią jest to, że obiekty często przedstawiają coś konkretnego, rzeczywistego lub konkretnego; dlatego posiadanie wielu odniesień do tej samej rzeczy jest niezwykle przydatne. W przeciwnym razie trudniej byłoby pisać programy.
Za każdym razem,
a
gdy przekazujesz jako argument / parametr do innej funkcji, np. Wywołaniafoo(Dog aDoggy);
lub zastosowania metody
a
, bazowy kod programu tworzy kopię odwołania, aby utworzyć drugie odwołanie do tego samego obiektu.Ponadto, jeśli kod z skopiowanym odwołaniem znajduje się w innym wątku, oba mogą być używane jednocześnie, aby uzyskać dostęp do tego samego obiektu.
Tak więc w najbardziej przydatnych programach będzie wiele odniesień do tego samego obiektu, ponieważ jest to semantyka większości języków programowania OO.
Teraz, jeśli się nad tym zastanowimy, ponieważ przekazywanie przez referencje jest jedynym mechanizmem dostępnym w wielu językach OO (C ++ obsługuje oba), możemy oczekiwać, że będzie to „prawidłowe” zachowanie domyślne .
IMHO, używanie referencji jest właściwym ustawieniem domyślnym z kilku powodów:
Istnieje również argument dotyczący wydajności; wykonywanie kopii całych obiektów jest mniej wydajne niż kopiowanie odwołania. Myślę jednak, że to nie ma sensu. Liczne odniesienia do tego samego obiektu mają większy sens i są łatwiejsze w użyciu, ponieważ pasują do semantyki prawdziwego świata fizycznego.
Tak więc, IMHO, zwykle ma sens wiele odniesień do tego samego obiektu. W nietypowych przypadkach, gdy nie ma to sensu w kontekście algorytmu, większość języków zapewnia możliwość „klonowania” lub głębokiej kopii. Nie jest to jednak domyślne.
Myślę, że ludzie, którzy twierdzą, że to nie powinno być domyślne, używają języka, który nie zapewnia automatycznego usuwania śmieci. Na przykład staroświecki C ++. Problem polega na tym, że muszą znaleźć sposób na zbieranie „martwych” przedmiotów, a nie na odzyskiwanie przedmiotów, które mogą być nadal potrzebne; posiadanie wielu odniesień do tego samego obiektu sprawia, że jest to trudne.
Myślę, że jeśli C ++ miał wystarczająco tanie wyrzucanie elementów bezużytecznych, aby wszystkie obiekty, do których istnieją odniesienia, były usuwane, to znaczna część sprzeciwu zniknęła. Nadal będą przypadki, w których semantyka odniesienia nie jest potrzebna. Jednak z mojego doświadczenia wynika, że ludzie, którzy potrafią zidentyfikować te sytuacje, zazwyczaj są w stanie wybrać odpowiednią semantykę.
Uważam, że istnieją pewne dowody na to, że duża część kodu w programie C ++ służy do obsługi lub łagodzenia usuwania śmieci. Jednak pisanie i utrzymywanie tego rodzaju „infrastrukturalnego” kodu zwiększa koszty; ma na celu ułatwienie korzystania z języka lub zwiększenie jego niezawodności. Na przykład język Go został zaprojektowany z naciskiem na usunięcie niektórych słabości C ++ i nie ma innego wyboru niż wyrzucanie elementów bezużytecznych.
Jest to oczywiście nieistotne w kontekście Java. To również zostało zaprojektowane tak, aby było łatwe w użyciu, podobnie jak zbieranie śmieci. Dlatego posiadanie wielu odniesień jest domyślną semantyką i jest względnie bezpieczne w tym sensie, że obiekty nie są odzyskiwane, gdy istnieje odniesienie do nich. Oczywiście mogą być trzymane przez strukturę danych, ponieważ program nie porządkuje właściwie, gdy naprawdę zakończy się na obiekcie.
Powracając do pytania (z pewnym uogólnieniem), kiedy chciałbyś mieć więcej niż jedno odniesienie do tego samego obiektu? Prawie w każdej sytuacji, o której mogę pomyśleć. Są one domyślną semantyką większości mechanizmów przekazywania parametrów języków. Sugeruję, że dzieje się tak, ponieważ domyślna semantyka obsługi obiektów, która istnieje w realnym świecie, musi być w zasadzie przez odniesienie (ponieważ rzeczywiste obiekty są tam).
Każda inna semantyka byłaby trudniejsza do opanowania.
Sugeruję, że „łazik”
dl
powinien być tym, na który wpływ mająsetOwner
lub programy będą miały trudności z pisaniem, zrozumieniem, debugowaniem lub modyfikacją. Myślę, że większość programistów byłaby zdziwiona lub przerażona inaczej.później pies jest sprzedawany:
Ten rodzaj przetwarzania jest powszechny i normalny. Tak więc semantyka odniesienia jest domyślna, ponieważ zazwyczaj ma ona największy sens.
Podsumowanie: Zawsze warto mieć wiele odwołań do tego samego obiektu.
źródło
Oczywiście jeszcze jeden scenariusz, w którym możesz skończyć z:
ma miejsce, gdy utrzymujesz kod i
b
kiedyś byłeś innym psem lub inną klasą, ale teraz jest obsługiwany przeza
.Ogólnie rzecz biorąc, w średnim okresie powinieneś przerobić cały kod, aby odwoływał się
a
bezpośrednio, ale może się to nie zdarzyć od razu.źródło
Chcesz tego w dowolnym momencie, gdy Twój program ma szansę wciągnąć byt do pamięci w więcej niż jednym miejscu, być może dlatego, że używają go różne komponenty.
Mapy tożsamości stanowiły ostateczny lokalny magazyn podmiotów, dzięki czemu możemy uniknąć posiadania dwóch lub więcej osobnych reprezentacji. Kiedy reprezentujemy ten sam obiekt dwa razy, nasz klient ryzykuje spowodowanie problemu z współbieżnością, jeśli jedno odwołanie do obiektu będzie się zmieniać jego stan, zanim zrobi to druga instancja. Chodzi o to, aby zapewnić, że nasz klient zawsze ma ostateczne odniesienie do naszego bytu / obiektu.
źródło
Użyłem tego, pisząc solver Sudoku. Kiedy znam numer komórki podczas przetwarzania wierszy, chcę, aby zawierająca kolumna znała również numer tej komórki podczas przetwarzania kolumn. Tak więc zarówno kolumny, jak i wiersze są tablicami obiektów Cell, które się nakładają. Dokładnie tak, jak pokazała zaakceptowana odpowiedź.
źródło
W aplikacjach webowych Object Relational Mappers może korzystać z leniwego ładowania, aby wszystkie odwołania do tego samego obiektu bazy danych (przynajmniej w tym samym wątku) wskazywały na to samo.
Na przykład, jeśli masz dwie tabele:
Psy:
Właściciele:
Istnieje kilka metod, które może wykonać ORM, jeśli wykonane zostaną następujące połączenia:
W naiwnej strategii wszystkie wywołania modeli i powiązane odniesienia pobierają oddzielne obiekty.
Dog.find()
,Owner.find()
,owner.dogs
, Adog.owner
wynik w bazie danych trafić za pierwszym razem, po czym są one zapisywane w pamięci. A więc:Bez odniesienia masz więcej pobrań, więcej pamięci i wprowadzasz możliwość nadpisywania wcześniejszych aktualizacji.
Załóżmy, że Twoja ORM wie, że wszystkie odniesienia do wiersza 1 tabeli psów powinny wskazywać na to samo. Następnie:
Innymi słowy, użycie referencji pomaga skodyfikować zasadę, że jednym punktem prawdy (przynajmniej w tym wątku) jest wiersz w bazie danych.
źródło