Spesso è necessario fare una copia di a valore in rubino. Anche se questo può sembrare semplice, ed è per oggetti semplici, non appena devi fare una copia di un dato struttura con più array o hash sullo stesso oggetto, scoprirai rapidamente che ce ne sono molti insidie.
Oggetti e riferimenti
Per capire cosa sta succedendo, diamo un'occhiata ad un semplice codice. Innanzitutto, l'operatore di assegnazione utilizza un tipo POD (Plain Old Data) Rubino.
a = 1
b = a
a + = 1
mette b
Qui, l'operatore di assegnazione sta eseguendo una copia del valore di un' e assegnandolo a B utilizzando l'operatore di assegnazione. Eventuali modifiche a un' non si rifletterà in B. Ma che dire di qualcosa di più complesso? Considera questo.
a = [1,2]
b = a
a << 3
mette b.inspect
Prima di eseguire il programma sopra, prova a indovinare quale sarà l'output e perché. Questo non è lo stesso dell'esempio precedente, modifiche apportate a un' si riflettono in B, ma perché? Questo perché il Vettore l'oggetto non è un tipo POD. L'operatore di assegnazione non esegue una copia del valore, ma semplicemente copia il valore
riferimento all'oggetto Array. Il un' e B le variabili sono ora Riferimenti allo stesso oggetto Array, eventuali cambiamenti in entrambe le variabili verranno visualizzati nell'altra.E ora puoi capire perché copiare oggetti non banali con riferimenti ad altri oggetti può essere complicato. Se fai semplicemente una copia dell'oggetto, stai solo copiando i riferimenti agli oggetti più profondi, quindi la tua copia viene definita "copia superficiale".
Cosa offre Ruby: dup e clone
Ruby fornisce due metodi per fare copie di oggetti, incluso uno che può essere fatto per fare copie profonde. Il Oggetto # dup Il metodo eseguirà una copia superficiale di un oggetto. Per raggiungere questo obiettivo, il dup Il metodo chiamerà il initialize_copy metodo di quella classe. Ciò che fa esattamente dipende dalla classe. In alcune classi, come Array, verrà inizializzato un nuovo array con gli stessi membri dell'array originale. Questo, tuttavia, non è una copia profonda. Considera quanto segue.
a = [1,2]
b = a.dup
a << 3
mette b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
mette b.inspect
Cosa è successo qui? Il Array # initialize_copy Il metodo farà davvero una copia di un array, ma quella copia è essa stessa una copia superficiale. Se si dispone di altri tipi non POD nell'array, utilizzare dup sarà solo una copia parzialmente profonda. Sarà profondo quanto il primo array, più profondo array, hash o altri oggetti verranno copiati solo in modo superficiale.
C'è un altro metodo degno di nota, clone. Il metodo clone fa la stessa cosa di dup con un'importante distinzione: si prevede che gli oggetti avranno la precedenza su questo metodo con uno che può eseguire copie approfondite.
Quindi in pratica cosa significa? Significa che ciascuna delle tue classi può definire un metodo clone che farà una copia profonda di quell'oggetto. Significa anche che devi scrivere un metodo clone per ogni classe che crei.
A Trick: Marshalling
"Marshalling" di un oggetto è un altro modo di dire "serializzare" un oggetto. In altre parole, trasforma quell'oggetto in un flusso di caratteri che può essere scritto in un file che puoi "riattivare" o "annullare la serializzazione" in seguito per ottenere lo stesso oggetto. Questo può essere sfruttato per ottenere una copia profonda di qualsiasi oggetto.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
mette b.inspect
Cosa è successo qui? Marshal.dump crea un "dump" dell'array nidificato archiviato in un'. Questo dump è una stringa di caratteri binari destinata a essere memorizzata in un file. Ospita l'intero contenuto dell'array, una copia completa completa. Il prossimo, Marshal.load fa il contrario. Analizza questa matrice di caratteri binari e crea una matrice completamente nuova, con elementi di matrice completamente nuovi.
Ma questo è un trucco. È inefficiente, non funzionerà su tutti gli oggetti (cosa succede se si tenta di clonare una connessione di rete in questo modo?) E probabilmente non è terribilmente veloce. Tuttavia, è il modo più semplice per rendere le copie profonde poco personalizzate initialize_copy o clone metodi. Inoltre, la stessa cosa può essere fatta con metodi come to_yaml o to_xml se hai librerie caricate per supportarle.