Kopiowanie obiektów
W programowaniu obiektowym kopiowanie obiektów polega na tworzeniu kopii istniejącego obiektu , jednostki danych w programowaniu obiektowym . Powstały obiekt nazywany jest kopią obiektu lub po prostu kopią oryginalnego obiektu. Kopiowanie jest proste, ale ma subtelności i może mieć znaczny narzut. Istnieje kilka sposobów kopiowania obiektu, najczęściej przez konstruktor kopiujący lub klonowanie . Kopiowanie odbywa się głównie po to, aby można było zmodyfikować lub przenieść kopię lub zachować bieżącą wartość. Jeśli którekolwiek z nich nie jest potrzebne, odniesienie do oryginalnych danych jest wystarczające i bardziej efektywne, ponieważ nie występuje kopiowanie.
Obiekty ogólnie przechowują złożone dane . Podczas gdy w prostych przypadkach kopiowanie można wykonać poprzez przydzielenie nowego, niezainicjowanego obiektu i skopiowanie wszystkich pól ( atrybutów ) z oryginalnego obiektu, w bardziej złożonych przypadkach nie skutkuje to pożądanym zachowaniem.
Metody kopiowania
Celem projektowym większości obiektów jest nadanie podobieństwa do bycia wykonanym z jednego monolitycznego bloku, mimo że większość tak nie jest. Ponieważ obiekty składają się z kilku różnych części, kopiowanie staje się nietrywialne. Istnieje kilka strategii leczenia tego problemu.
Rozważmy obiekt A, który zawiera pola x i (konkretniej, rozważmy, czy A jest łańcuchem, a x i jest tablicą jego znaków). Istnieją różne strategie tworzenia kopii A, zwane kopią płytką i kopią głęboką . Wiele języków pozwala na ogólne kopiowanie za pomocą jednej lub obu strategii, definiując albo jedną operację kopiowania , albo oddzielne operacje kopiowania płytkiego i głębokiego . Zauważ, że jeszcze płytsze jest użycie odniesienia do istniejącego obiektu A, w którym to przypadku nie ma nowego obiektu, tylko nowe odniesienie.
Terminologia płytkiej i głębokiej kopii pochodzi ze Smalltalk -80. To samo rozróżnienie dotyczy porównywania obiektów pod kątem równości: zasadniczo istnieje różnica między tożsamością (ten sam obiekt) a równością (ta sama wartość), odpowiadająca płytkiej równości i (1 poziom) głębokiej równości dwóch odniesień do obiektów, ale dalej, czy równość oznacza porównanie tylko pól danego obiektu lub wyłuskanie niektórych lub wszystkich pól i porównanie ich wartości po kolei (np. czy dwie połączone listy są równe, jeśli mają te same węzły lub jeśli mają te same wartości?). [ wymagane wyjaśnienie ]
Płytka kopia
Jedną z metod kopiowania obiektu jest płytka kopia . W takim przypadku tworzony jest nowy obiekt B , a wartości pól A są kopiowane do obiektu B. Jest to również znane jako kopiowanie pole po polu , kopiowanie pole po polu lub kopiowanie pole . Jeśli wartość pola jest odniesieniem do obiektu (np. adresu pamięci), kopiuje odniesienie, a więc odwołuje się do tego samego obiektu co A, a jeśli wartość pola jest typu pierwotnego, kopiuje wartość typu pierwotnego. W językach bez typów pierwotnych (gdzie wszystko jest obiektem) wszystkie pola kopii B są odniesieniami do tych samych obiektów, co pola oryginału A. Obiekty, do których się odwołujemy, są zatem shared , więc jeśli jeden z tych obiektów zostanie zmodyfikowany (z A lub B), zmiana będzie widoczna w drugim. Płytkie kopie są proste i zazwyczaj tanie, ponieważ zwykle można je zaimplementować, po prostu dokładnie kopiując bity.
Głęboka kopia
Alternatywą jest głęboka kopia, co oznacza, że pola są dereferencjonowane: zamiast odniesień do kopiowanych obiektów, tworzone są nowe obiekty kopii dla wszelkich obiektów, do których istnieją odniesienia, a odniesienia do nich są umieszczane w B. Wynik różni się od wyniku płytkiej kopii daje to, że obiekty, do których odwołuje się kopia B, są niezależne od tych, do których odwołuje się A. Głębokie kopie są droższe ze względu na konieczność tworzenia dodatkowych obiektów i mogą być znacznie bardziej skomplikowane ze względu na odniesienia, które mogą tworzyć skomplikowany wykres.
Głębokie kopiowanie to proces, w którym proces kopiowania odbywa się rekurencyjnie. Oznacza to najpierw skonstruowanie nowego obiektu kolekcji, a następnie rekurencyjne zapełnienie go kopiami obiektów potomnych znalezionych w oryginale. W przypadku głębokiej kopii kopia obiektu jest kopiowana do innego obiektu. Oznacza to, że wszelkie zmiany dokonane w kopii obiektu nie mają odzwierciedlenia w obiekcie oryginalnym. W Pythonie jest to realizowane za pomocą funkcji „deepcopy()”.
Połączenie
W bardziej złożonych przypadkach niektóre pola w kopii powinny mieć wspólne wartości z oryginalnym obiektem (jak w płytkiej kopii), co odpowiada relacji „powiązania”; a niektóre pola powinny mieć kopie (jak w głębokiej kopii), odpowiadające relacji „agregacji”. W takich przypadkach na ogół wymagana jest niestandardowa implementacja kopiowania; ten problem i rozwiązanie pochodzą ze Smalltalk-80. Alternatywnie pola można oznaczyć jako wymagające płytkiej lub głębokiej kopii i automatycznie wygenerować operacje kopiowania (podobnie jak w przypadku operacji porównania). Nie jest to jednak zaimplementowane w większości języków zorientowanych obiektowo, chociaż w Eiffel jest częściowe wsparcie.
Realizacja
Prawie wszystkie języki programowania zorientowanego obiektowo zapewniają jakiś sposób kopiowania obiektów. Ponieważ większość języków nie zapewnia większości obiektów dla programów, programista musi określić, w jaki sposób obiekt powinien zostać skopiowany, tak samo jak przede wszystkim musi określić, czy dwa obiekty są identyczne, a nawet porównywalne. Wiele języków zapewnia pewne zachowanie domyślne.
Sposób rozwiązania kopiowania różni się w zależności od języka i jaką ma koncepcję obiektu.
Leniwa kopia
Leniwa kopia to implementacja głębokiej kopii. Podczas początkowego kopiowania obiektu używana jest (szybka) płytka kopia. Licznik służy również do śledzenia, ile obiektów współdzieli dane. Gdy program chce zmodyfikować obiekt, może określić, czy dane są udostępniane (poprzez sprawdzenie licznika) i w razie potrzeby może wykonać głęboką kopię.
Leniwa kopia wygląda na zewnątrz jak głęboka kopia, ale wykorzystuje szybkość płytkiej kopii, gdy tylko jest to możliwe. Minusem są dość wysokie, ale stałe koszty bazowe ze względu na licznik. Ponadto w niektórych sytuacjach odniesienia cykliczne mogą powodować problemy.
Leniwa kopia jest powiązana z kopiowaniem przy zapisie .
w Jawie
Poniżej przedstawiono przykłady dla jednego z najczęściej używanych języków zorientowanych obiektowo, Java , które powinny obejmować prawie każdy sposób, w jaki język obiektowy może rozwiązać ten problem.
Inaczej niż w C++, obiekty w Javie są zawsze dostępne pośrednio poprzez referencje . Obiekty nigdy nie są tworzone niejawnie, ale zawsze są przekazywane lub przypisywane przez zmienną referencyjną. (Metody w Javie są zawsze przekazywane przez wartość , jednak przekazywana jest wartość zmiennej referencyjnej). Wirtualna maszyna Java zarządza wyrzucaniem elementów bezużytecznych , dzięki czemu obiekty są czyszczone, gdy nie są już osiągalne. Nie ma automatycznego sposobu na skopiowanie dowolnego obiektu w Javie.
Kopiowanie jest zwykle wykonywane przez metodę clone() klasy. Ta metoda zwykle z kolei wywołuje metodę clone() swojej klasy nadrzędnej w celu uzyskania kopii, a następnie wykonuje niestandardowe procedury kopiowania. Ostatecznie dochodzi to do metody clone() klasy Object
(najwyższej klasy), która tworzy nową instancję tej samej klasy co obiekt i kopiuje wszystkie pola do nowej instancji („płytka kopia”). Jeśli ta metoda jest używana, klasa musi zaimplementować Cloneable
, w przeciwnym razie zostanie zgłoszona CloneNotSupportedException. Po uzyskaniu kopii z klasy nadrzędnej, własna metoda clone() klasy może zapewnić niestandardowe możliwości klonowania, takie jak głębokie kopiowanie (tj. duplikowanie niektórych struktur, do których odnosi się obiekt) lub nadanie nowej instancji nowego unikalnego identyfikatora.
Typ zwracany przez clone() to Object
, ale implementatorzy metody klonowania mogą zamiast tego napisać typ klonowanego obiektu ze względu na obsługę kowariantnych typów zwracanych przez Javę . Jedną z zalet używania clone() jest to, że ponieważ jest to metoda nadrzędna , możemy wywołać clone() na dowolnym obiekcie i użyje on metody clone() swojej klasy, bez konieczności znajomości przez wywołujący kod, czym jest ta klasa (co byłoby potrzebne w przypadku konstruktora kopiującego).
Wadą jest to, że często nie można uzyskać dostępu do metody clone() na typie abstrakcyjnym. Większość interfejsów i klas abstrakcyjnych w Javie nie określaj metody public clone(). Dlatego często jedynym sposobem użycia metody clone() jest znajomość klasy obiektu, co jest sprzeczne z zasadą abstrakcji polegającą na użyciu możliwie najbardziej ogólnego typu. Na przykład, jeśli ktoś ma odwołanie List w Javie, nie można wywołać clone() dla tego odwołania, ponieważ List nie określa żadnej publicznej metody clone(). Implementacje List, takie jak ArrayList i LinkedList, generalnie mają metody clone(), ale noszenie klasy typu obiektu jest niewygodne i złe.
Innym sposobem kopiowania obiektów w Javie jest ich serializacja za pomocą interfejsu Serializable
. Jest to zwykle używane do trwałości i protokołu przewodowego , ale tworzy kopie obiektów i, w przeciwieństwie do klonowania, głęboka kopia, która z wdziękiem obsługuje cykliczne wykresy obiektów, jest łatwo dostępna przy minimalnym wysiłku programisty.
Obie te metody mają znaczący problem: konstruktor nie jest używany do obiektów kopiowanych za pomocą klonowania lub serializacji. Może to prowadzić do błędów z nieprawidłowo zainicjowanymi danymi, uniemożliwia użycie końcowych
pól składowych i utrudnia konserwację. Niektóre narzędzia próbują przezwyciężyć te problemy, używając odbicia do głębokiego kopiowania obiektów, na przykład biblioteki głębokiego klonowania.
w Eiffel
Obiekty środowiska wykonawczego w Eiffel są dostępne pośrednio poprzez odniesienia lub jako rozszerzone obiekty, których pola są osadzone w obiektach, które ich używają. Oznacza to, że pola obiektu są przechowywane zewnętrznie lub wewnętrznie .
Klasa Eiffla ANY
zawiera funkcje do płytkiego i głębokiego kopiowania oraz klonowania obiektów. Wszystkie klasy Eiffla dziedziczą z ANY
, więc te funkcje są dostępne we wszystkich klasach i mają zastosowanie zarówno do obiektów referencyjnych, jak i rozszerzonych.
kopiowania wpływa na płytką kopię pola po polu z jednego obiektu do drugiego .
W takim przypadku nie jest tworzony żaden nowy obiekt. Jeśli y
zostały skopiowane do x
, to te same obiekty, do których odwołuje się y
przed zastosowaniem copy
, będą również odwoływane przez x
po zakończeniu funkcji kopiowania
.
Aby spowodować utworzenie nowego obiektu, który jest płytkim duplikatem y ,
używany jest bliźniak
funkcji . W takim przypadku tworzony jest jeden nowy obiekt z polami identycznymi z polami źródła.
Bliźniak
funkcji opiera się na kopii
funkcji , którą w razie potrzeby można przedefiniować w potomkach ANY
. Wynik bliźniaka
jest typu zakotwiczonego, takiego jak Current
.
Głębokie kopiowanie i tworzenie głębokich bliźniaków można wykonać za pomocą funkcji deep_copy
i deep_twin
, ponownie odziedziczonych z klasy ANY
. Cechy te mają potencjał tworzenia wielu nowych obiektów, ponieważ powielają wszystkie obiekty w całej strukturze obiektu. Ponieważ tworzone są nowe zduplikowane obiekty zamiast po prostu kopiować odniesienia do istniejących obiektów, głębokie operacje staną się źródłem problemów z wydajnością łatwiej niż operacje płytkie.
W innych językach
W języku C# zamiast interfejsu ICloneable
można użyć ogólnej metody rozszerzenia, aby utworzyć głęboką kopię przy użyciu odbicia. Ma to dwie zalety: po pierwsze zapewnia elastyczność kopiowania każdego obiektu bez konieczności ręcznego określania każdej właściwości i zmiennej do skopiowania. Po drugie, ponieważ typ jest ogólny, kompilator zapewnia, że obiekt docelowy i obiekt źródłowy mają ten sam typ.
W Objective-C metody copy
i mutableCopy
są dziedziczone przez wszystkie obiekty i przeznaczone do wykonywania kopii; ten ostatni służy do tworzenia zmiennego typu oryginalnego obiektu. Te metody z kolei wywołują copyWithZone
i mutableCopyWithZone
w celu wykonania kopiowania. Obiekt musi implementować odpowiednią copyWithZone
, aby można go było kopiować.
W OCaml funkcja biblioteczna Oo.copy wykonuje płytkie kopiowanie obiektu .
W Pythonie moduł kopiowania biblioteki udostępnia płytką i głęboką kopię obiektów odpowiednio za pomocą funkcji copy()
i deepcopy()
. Programiści mogą zdefiniować specjalne metody __copy__()
i __deepcopy__()
w obiekcie, aby zapewnić niestandardową implementację kopiowania.
W Ruby wszystkie obiekty dziedziczą dwie metody wykonywania płytkich kopii, clone i dup . Te dwie metody różnią się tym, że klon
kopiuje skażony stan obiektu, stan zamrożenia i wszelkie metody singleton , które może mieć, podczas gdy dup
kopiuje tylko jego skażony stan. Głębokie kopie można uzyskać przez zrzucanie i ładowanie strumienia bajtów obiektu lub serializację YAML. [1] Alternatywnie możesz użyć klejnotu deep_dive, aby wykonać kontrolowaną głęboką kopię wykresów obiektów. [2]
W Perlu struktury zagnieżdżone są przechowywane przy użyciu referencji, więc programista może albo przejść przez całą strukturę i ponownie odnieść się do danych, albo użyć funkcji dclone ()
z modułu Storable .
W VBA przypisanie zmiennych typu Object
jest kopią płytką, przypisanie wszystkich innych typów (typy numeryczne, łańcuchowe, typy zdefiniowane przez użytkownika, tablice) jest kopią głęboką. Tak więc słowo kluczowe Ustaw
dla zadania sygnalizuje płytką kopię, a (opcjonalne) słowo kluczowe Let
sygnalizuje głęboką kopię. Nie ma wbudowanej metody dla głębokich kopii obiektów w VBA.
Zobacz też
- Konstruktor kopiujący
- Przeciążenie operatora
- Liczenie referencji
- Kopiowanie przy zapisie
- Klon (metoda Java)
Notatki
- Goldberg, Adele ; Robson, David (1983). Smalltalk-80: język i jego implementacja . Palo Alto, Kalifornia: Centrum badawcze Xerox Palo Alto. ISBN 978-0-201-11371-6 .
- Grogono, Piotr; Sakkinen, Markku (12 maja 2000). „Kopiowanie i porównywanie: problemy i rozwiązania” (PDF) . W Elisie Bertino (red.). Notatki z wykładów z informatyki . ECOOP 2000 — Programowanie obiektowe. Tom. 1850. Springer Berlin Heidelberg. s. 226–250. doi : 10.1007/3-540-45102-1_11 . Źródło 2015-06-23 .