Często konieczne jest wykonanie kopii pliku wartość w Ruby. Choć może się to wydawać proste, i dotyczy prostych obiektów, gdy tylko trzeba wykonać kopię danych struktura z wieloma tablicami lub skrótami na tym samym obiekcie, szybko okaże się, że jest ich wiele pułapki.
Obiekty i referencje
Aby zrozumieć, co się dzieje, spójrzmy na prosty kod. Najpierw operator przypisania używający POD (Plain Old Data) wpisz Rubin.
a = 1
b = a
a + = 1
stawia b
Tutaj operator przypisania tworzy kopię wartości za i przypisując go do b za pomocą operatora przypisania. Wszelkie zmiany w za nie zostanie odzwierciedlone w b. Ale co z czymś bardziej złożonym? Rozważ to.
a = [1,2]
b = a
<< 3
stawia b.inspect
Przed uruchomieniem powyższego programu spróbuj zgadnąć, jakie będzie wyjście i dlaczego. To nie to samo co w poprzednim przykładzie, zmiany wprowadzone w za znajdują odzwierciedlenie w b, ale dlaczego? Jest tak, ponieważ Szyk obiekt nie jest typem POD. Operator przypisania nie tworzy kopii wartości, po prostu kopiuje
odniesienie do obiektu Array. The za i b zmienne są teraz Bibliografia do tego samego obiektu Array, wszelkie zmiany w jednej zmiennej będą widoczne w drugiej.Teraz możesz zobaczyć, dlaczego kopiowanie nietrywialnych obiektów z odniesieniami do innych obiektów może być trudne. Jeśli po prostu wykonasz kopię obiektu, po prostu kopiujesz odniesienia do głębszych obiektów, więc twoja kopia jest określana jako „płytka kopia”.
Co zapewnia Ruby: duplikuj i klonuj
Ruby udostępnia dwie metody tworzenia kopii obiektów, w tym jedną, która umożliwia wykonywanie głębokich kopii. The Obiekt # dup Metoda utworzy płytką kopię obiektu. Aby to osiągnąć, dup Metoda wywoła metodę initialize_copy metoda tej klasy. To, co to dokładnie robi, zależy od klasy. W niektórych klasach, takich jak Array, zainicjuje nową tablicę z tymi samymi elementami, co tablica oryginalna. Nie jest to jednak głęboka kopia. Rozważ następujące.
a = [1,2]
b = a.dup
<< 3
stawia b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
stawia b.inspect
Co się tu stało? The Tablica # initialize_copy metoda rzeczywiście utworzy kopię tablicy, ale sama ta kopia jest płytką kopią. Jeśli masz w tablicy inne typy niż POD, użyj dup będzie tylko częściowo głęboką kopią. Będzie tylko tak głęboka jak pierwsza tablica, jeszcze głębiej tablice, hasze lub inne obiekty zostaną skopiowane tylko płytko.
Warto wspomnieć o innej metodzie: klon. Metoda klonowania robi to samo co dup z jednym ważnym rozróżnieniem: oczekuje się, że obiekty zastąpią tę metodę metodą, która może wykonywać głębokie kopie.
Więc w praktyce co to oznacza? Oznacza to, że każda z twoich klas może zdefiniować metodę klonowania, która utworzy głęboką kopię tego obiektu. Oznacza to również, że musisz napisać metodę klonowania dla każdej klasy, którą utworzysz.
Sztuczka: Marshalling
„Marshalling” obiektu to inny sposób powiedzenia „serializacji” obiektu. Innymi słowy, zamień ten obiekt w strumień znaków, który można zapisać w pliku, który możesz później „odmarsić” lub „cofnąć serializację”, aby uzyskać ten sam obiekt. Można to wykorzystać do uzyskania głębokiej kopii dowolnego obiektu.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
stawia b.inspect
Co się tu stało? Marshal.dump tworzy „zrzut” zagnieżdżonej tablicy przechowywanej w za. Ten zrzut jest ciągiem znaków binarnych przeznaczonych do przechowywania w pliku. Zawiera pełną zawartość tablicy, kompletną, głęboką kopię. Kolejny, Marshal.load robi coś przeciwnego. Analizuje tablicę znaków binarnych i tworzy zupełnie nową tablicę z całkowicie nowymi elementami tablicy.
Ale to sztuczka. Jest nieefektywny, nie działa na wszystkich obiektach (co się stanie, jeśli spróbujesz sklonować połączenie sieciowe w ten sposób?) I prawdopodobnie nie będzie strasznie szybki. Jest to jednak najłatwiejszy sposób na wykonanie głębokich kopii bez niestandardowych initialize_copy lub klon metody To samo można zrobić z metodami takimi jak to_yaml lub to_xml jeśli masz załadowane biblioteki do ich obsługi.