Det er ofte nødvendigt at lave en kopi af en værdi i Ruby. Selvom dette kan virke enkelt, og det er til enkle objekter, så snart du er nødt til at lave en kopi af en data struktur med flere array eller hashes på det samme objekt, vil du hurtigt finde, at der er mange faldgruber.
Objekter og referencer
Lad os se på en enkel kode for at forstå, hvad der foregår. Først tildelingsoperatøren, der bruger en POD (Plain Old Data) -type Rubin.
a = 1
b = a
a + = 1
sætter b
Her opretter tildelingsoperatøren en kopi af værdien af -en og tildele den til b ved hjælp af tildelingsoperatøren. Eventuelle ændringer til -en vil ikke blive reflekteret i b. Men hvad med noget mere komplekst? Overvej dette.
a = [1,2]
b = a
a << 3
sætter b.inspect
Inden du kører ovenstående program, skal du prøve at gætte, hvad output bliver, og hvorfor. Dette er ikke det samme som det forrige eksempel, ændringer foretaget til -en afspejles i b, men hvorfor? Dette skyldes, at Array objektet er ikke en POD-type. Tildelingsoperatøren laver ikke en kopi af værdien, den kopierer blot
reference til Array-objektet. Det -en og b variabler er nu referencer til det samme Array-objekt, vil alle ændringer i begge variabler ses i det andet.Og nu kan du se, hvorfor det kan være vanskeligt at kopiere ikke-trivielle objekter med henvisninger til andre objekter. Hvis du blot laver en kopi af objektet, kopierer du bare henvisningerne til de dybere objekter, så din kopi kaldes en "lav kopi."
Hvad Ruby giver: dup og klon
Ruby leverer to metoder til at fremstille kopier af objekter, herunder en, der kan laves til at kopiere dybe kopier. Det Objekt # dup metoden fremstiller en lav kopi af et objekt. For at opnå dette dUP metoden kalder initialize_copy metode til denne klasse. Hvad dette gør nøjagtigt, afhænger af klassen. I nogle klasser, såsom Array, initialiseres det en ny matrix med de samme medlemmer som den originale matrix. Dette er dog ikke en dyb kopi. Overvej følgende.
a = [1,2]
b = a.dup
a << 3
sætter b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
sætter b.inspect
Hvad er der sket her? Det Array # initialize_copy metode vil faktisk lave en kopi af en matrix, men den kopi er i sig selv en lav kopi. Hvis du har andre ikke-POD-typer i dit array, skal du bruge dUP vil kun være en delvist dyb kopi. Det vil kun være så dybt som det første array, enhver dybere arrays, hashes eller andre objekter kopieres kun lavt.
Der er en anden metode, der er værd at nævne, klon. Klonmetoden gør det samme som dUP med en vigtig forskel: Det forventes, at objekter tilsidesætter denne metode med en, der kan udføre dybe kopier.
Så hvad betyder dette i praksis? Det betyder, at hver af dine klasser kan definere en klonemetode, der vil lave en dyb kopi af dette objekt. Det betyder også, at du skal skrive en klonemetode for hver eneste klasse, du laver.
En trick: Marshalling
"Marshalling" af et objekt er en anden måde at sige "serialisering" af et objekt på. Med andre ord skal du ændre dette objekt til en tegnstrøm, der kan skrives til en fil, som du kan "fjerne" eller "fjerne tekst" senere for at få det samme objekt. Dette kan udnyttes til at få en dyb kopi af ethvert objekt.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
sætter b.inspect
Hvad er der sket her? Marshal.dump opretter en "dump" af det indlejrede array, der er gemt i -en. Dette dump er en binær tegnstreng, der er beregnet til at blive gemt i en fil. Det huser det fulde indhold af arrayet, en komplet dyb kopi. Næste, Marshal.load gør det modsatte. Den analyserer denne binære karaktergruppe og opretter en helt ny matrix med helt nye Array-elementer.
Men dette er et trick. Det er ineffektivt, det fungerer ikke på alle objekter (hvad sker der, hvis du prøver at klone en netværksforbindelse på denne måde?) Og det er sandsynligvis ikke meget hurtigt. Det er dog den nemmeste måde at gøre dybe kopier på, som ikke er tilpasset initialize_copy eller klon metoder. Den samme ting kan også gøres med metoder som to_yaml eller to_xml hvis du har biblioteker indlæst for at understøtte dem.