Često je potrebno napraviti kopiju vrijednost u Ruby. Iako se ovo može činiti jednostavnim, a to je za jednostavne predmete, čim morate napraviti kopiju podataka struktura s višestrukim nizovima ili heševima na istom objektu, brzo ćete ustanoviti da ih ima mnogo zamke.
Objekti i reference
Da bismo razumjeli što se događa, pogledajmo neki jednostavan kod. Prvo, operater dodjele pomoću vrste POD (Obični stari podaci) u Rubin.
a = 1
b = a
a + = 1
stavlja b
Ovdje operator dodjele izrađuje kopiju vrijednosti i dodijeliti ga b pomoću operatora dodjele. Sve promjene u neće se odraziti na b. Ali što je s nečim složenijim? Razmislite o ovome.
a = [1,2]
b = a
a << 3
stavlja b.inspect
Prije pokretanja gornjeg programa, pokušajte pogoditi koliki će biti izlaz i zašto. Ovo nije isto kao u prethodnom primjeru, učinjene promjene ogledaju se u b, ali zašto? To je zato što red objekt nije POD tip. Operator dodjele ne pravi kopiju vrijednosti, već jednostavno kopira upućivanje do objekta Array. i b sada su varijable reference istom objektu Array, sve promjene u bilo kojoj varijabli vidjet će se u drugoj.
A sada možete vidjeti zašto kopiranje ne-trivijalnih objekata s referencama na druge objekte može biti teško. Ako jednostavno napravite kopiju objekta, vi samo kopirate reference na dublje predmete, pa se vaša kopija naziva "plitkom kopijom".
Što Ruby pruža: dup i klon
Ruby pruža dvije metode za izradu kopija predmeta, uključujući i onu koja se može napraviti u dubokoj kopiji. Objekt # DUP metoda će napraviti plitku kopiju objekta. Da bi se to postiglo, The DUP metoda će pozvati initialize_copy metoda te klase. Što se to točno događa ovisi o klasi. U nekim će razredima, kao što je Array, inicijalizirati novi niz s istim članovima kao i izvorni niz. Ovo, međutim, nije duboka kopija. Razmotrite sljedeće.
a = [1,2]
b = a.dup
a << 3
stavlja b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
stavlja b.inspect
Što se ovdje dogodilo? Array # initialize_copy metoda će doista napraviti kopiju matrice, ali ta je kopija sama po sebi plitka. Ako imate bilo koje druge vrste koje nisu POD, u svom polju, koristeći DUP bit će samo djelomično duboka kopija. Bit će samo dubok kao prvi niz, bilo koji dublji nizovi, raspršivanja ili će se drugi predmeti tek plitko kopirati.
Postoji još jedna metoda koju vrijedi spomenuti, klon. Metoda klona čini istu stvar kao DUP s jednom važnom razlikom: očekuje se da će objekti nadvladati ovu metodu s onom koja može napraviti duboke kopije.
Pa u praksi što to znači? Znači da svaka od vaših klasa može definirati metodu kloniranja koja će napraviti duboku kopiju tog objekta. To također znači da morate napisati metodu kloniranja za svaki razred koji sastavite.
Trik: Marshalling
"Marshalling" objekta drugi je način kazivanja "serialization" objekta. Drugim riječima, pretvorite taj objekt u tok znakova koji se može zapisati u datoteku koju kasnije možete "skinuti maršalu" ili "neserializirati" da biste dobili isti objekt. Ovo se može iskoristiti za dobivanje duboke kopije bilo kojeg predmeta.
a = [[1,2]]
b = maršal.load (maršal.dump (a))
a [0] << 3
stavlja b.inspect
Što se ovdje dogodilo? Marshal.dump stvara "dump" ugniježđenog niza pohranjenog u . Ovaj dump je binarni niz znakova namijenjen za pohranjivanje u datoteku. U njemu se nalazi cijeli sadržaj niza, potpuna duboka kopija. Sljedeći, Marshal.load čini suprotno. Analizira ovaj niz binarnih znakova i stvara potpuno novi Array, s potpuno novim elementima Array.
Ali ovo je trik. Neefikasan je, neće raditi na svim objektima (što se događa ako pokušate klonirati mrežnu vezu na ovaj način?) I vjerojatno nije strašno brzo. Međutim, to je najjednostavniji način da duboke kopije nemaju običaj initialize_copy ili klon metode. Isto se može učiniti s metodama poput to_yaml ili to_xml ako imate učitane knjižnice koje ih podržavaju.