Hvordan lage dype kopier i Ruby

Det er ofte nødvendig å lage en kopi av en verdi i Ruby. Selv om dette kan virke enkelt, og det er for enkle objekter, så snart du må lage en kopi av en data struktur med flere array eller hashes på samme objekt, vil du raskt finne at det er mange fallgruver.

Objekter og referanser

La oss se på noen enkle koder for å forstå hva som skjer. Først tilordner operatøren som bruker en POD (Plain Old Data) -type Rubin.

a = 1
b = a
a + = 1
setter b

Her lager oppdragsoperatøren en kopi av verdien av en og tilordne den til b bruker oppdragsoperatøren. Eventuelle endringer i en vil ikke bli reflektert i b. Men hva med noe mer sammensatt? Vurder dette.

a = [1,2]
b = a
a << 3
setter b.inspect

Før du kjører ovennevnte program, kan du prøve å gjette hva utdataene blir og hvorfor. Dette er ikke det samme som forrige eksempel, endringer gjort en gjenspeiles i b, men hvorfor? Dette er fordi Array objektet er ikke en POD-type. Oppdragsoperatøren lager ikke en kopi av verdien, den kopierer bare henvisning til Array-objektet. De

instagram viewer
en og b variabler er nå referanser til samme Array-objekt, vil eventuelle endringer i begge variablene sees i det andre.

Og nå kan du se hvorfor det kan være vanskelig å kopiere ikke-trivielle objekter med referanser til andre objekter. Hvis du bare lager en kopi av objektet, kopierer du bare referansene til de dypere objektene, så kopien din blir referert til som en "grunne kopi."

Hva Ruby gir: dup og klon

Ruby gir to metoder for å lage kopier av objekter, inkludert en som kan lages til å lage dype kopier. De Object # DUP metoden vil lage en grunne kopi av et objekt. For å oppnå dette, DUP metoden vil kalle initialize_copy metode for den klassen. Hva dette gjør nøyaktig er avhengig av klassen. I noen klasser, for eksempel Array, vil den initialisere en ny matrise med de samme medlemmene som den opprinnelige arrayen. Dette er imidlertid ikke en dyp kopi. Vurder følgende.

a = [1,2]
b = a.dup
a << 3
setter b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
setter b.inspect

Hva har skjedd her? De Array # initialize_copy metoden vil faktisk lage en kopi av en matrise, men den kopien er i seg selv en grunne kopi. Hvis du har andre ikke-POD-typer i matrisen din, bruker du DUP vil bare være en delvis dyp kopi. Det vil bare være så dypt som det første arrayet, noe dypere arrays, hashes eller andre objekter vil bare bli kopiert.

Det er en annen metode som er verdt å nevne, klone. Klonmetoden gjør det samme som DUP med ett viktig skille: det forventes at objekter vil overstyre denne metoden med en som kan gjøre dype kopier.

Så i praksis hva betyr dette? Det betyr at hver av klassene dine kan definere en klonemetode som vil lage en dyp kopi av det objektet. Det betyr også at du må skrive en klonemetode for hver klasse du gjør.

A Trick: Marshalling

"Marshalling" av et objekt er en annen måte å si "serialisering" av et objekt på. Med andre ord, gjør det objektet til en karakterstrøm som kan skrives til en fil som du kan "fjerne" eller "fjerne merking" senere for å få det samme objektet. Dette kan utnyttes for å få en dyp kopi av ethvert objekt.

a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
setter b.inspect

Hva har skjedd her? Marshal.dump oppretter en "dump" av det nestede arrayet som er lagret i en. Denne dumpen er en binær tegnstreng som er ment å lagres i en fil. Den inneholder hele innholdet i matrisen, en fullstendig dyp kopi. Neste, Marshal.load gjør det motsatte. Den analyserer denne binære karaktergruppen og lager en helt ny matrise, med helt nye Array-elementer.

Men dette er et triks. Det er ineffektivt, det vil ikke fungere på alle objekter (hva skjer hvis du prøver å klone en nettverkstilkobling på denne måten?), Og det er sannsynligvis ikke veldig raskt. Imidlertid er det den enkleste måten å lage dype kopier på enn tilpasset initialize_copy eller klone metoder. Det samme kan også gjøres med metoder som to_yaml eller to_xml hvis du har biblioteker lastet inn for å støtte dem.