Είναι συχνά απαραίτητο να κάνετε ένα αντίγραφο ενός αξία στο Ruby. Ενώ αυτό μπορεί να φαίνεται απλό, και είναι για απλά αντικείμενα, μόλις πρέπει να κάνετε ένα αντίγραφο ενός δεδομένου δομή με πολλαπλούς πίνακες ή hashes στο ίδιο αντικείμενο, θα βρείτε γρήγορα ότι υπάρχουν πολλά παγίδες.
Αντικείμενα και Αναφορές
Για να καταλάβουμε τι συμβαίνει, ας δούμε έναν απλό κώδικα. Πρώτον, ο χειριστής αντιστοίχισης χρησιμοποιώντας έναν τύπο POD (Απλή παλιά δεδομένα) στο Ρουμπίνι.
α = 1
b = α
α + = 1
βάζει β
Εδώ, ο διαχειριστής εκχώρησης δημιουργεί αντίγραφο της τιμής του ένα και την εκχώρηση σι χρησιμοποιώντας τον χειριστή εκχώρησης. Οποιεσδήποτε αλλαγές στο ένα δεν θα αντικατοπτρίζεται σε σι. Αλλά τι γίνεται με κάτι πιο περίπλοκο; Σκεφτείτε αυτό.
α = [1,2]
b = α
ένα << 3
θέτει b.inspect
Πριν εκτελέσετε το παραπάνω πρόγραμμα, προσπαθήστε να μαντέψετε ποια θα είναι η έξοδος και γιατί. Αυτό δεν είναι το ίδιο με το προηγούμενο παράδειγμα, οι αλλαγές που πραγματοποιήθηκαν ένα αντανακλώνται στο σι, μα γιατί? Αυτό συμβαίνει επειδή το
Πίνακας το αντικείμενο δεν είναι τύπος POD. Ο διαχειριστής εκχώρησης δεν κάνει αντίγραφο της τιμής, απλά αντιγράφει το αναφορά στο αντικείμενο Array. ο ένα και σι οι μεταβλητές είναι τώρα βιβλιογραφικές αναφορές στο ίδιο αντικείμενο Array, οι αλλαγές σε οποιαδήποτε από τις δύο μεταβλητές θα εμφανιστούν στο άλλο.Και τώρα μπορείτε να δείτε γιατί η αντιγραφή μη τετριμμένων αντικειμένων με αναφορές σε άλλα αντικείμενα μπορεί να είναι δύσκολη. Αν κάνετε απλά ένα αντίγραφο του αντικειμένου, απλά αντιγράφετε τις αναφορές στα βαθύτερα αντικείμενα, επομένως το αντίγραφό σας αναφέρεται ως "ρηχό αντίγραφο".
Τι παρέχει το Ruby: dup και κλωνοποίηση
Το Ruby παρέχει δύο μεθόδους για την παραγωγή αντιγράφων αντικειμένων, συμπεριλαμβανομένου ενός που μπορεί να γίνει για να κάνει βαθιά αντίγραφα. ο Αντικείμενο # dup μέθοδος θα κάνει ένα ρηχό αντίγραφο ενός αντικειμένου. Για να επιτευχθεί αυτό, το dup η μέθοδος θα καλέσει το initialize_copy μέθοδο της τάξης αυτής. Τι ακριβώς κάνει αυτό εξαρτάται από την τάξη. Σε ορισμένες κλάσεις, όπως το Array, θα αρχικοποιηθεί ένας νέος πίνακας με τα ίδια μέλη με τον αρχικό πίνακα. Αυτό, ωστόσο, δεν είναι ένα βαθύ αντίγραφο. Σκέψου τα ακόλουθα.
α = [1,2]
b = a.dup
ένα << 3
θέτει b.inspect
α = [[1,2]]
b = a.dup
a [0] << 3
θέτει b.inspect
Τι συνέβη εδώ; ο Array # initialize_copy η μέθοδος θα κάνει πράγματι ένα αντίγραφο ενός Array, αλλά αυτό το αντίγραφο είναι το ίδιο ένα ρηχό αντίγραφο. Αν έχετε άλλους τύπους μη-POD στη συστοιχία σας, χρησιμοποιήστε το dup θα είναι μόνο ένα μερικώς βαθύ αντίγραφο. Θα είναι μόνο τόσο βαθιά όσο η πρώτη σειρά, οποιαδήποτε βαθύτερη συστοιχίες, hashes ή άλλα αντικείμενα θα είναι μόνο ρηχά αντιγραφεί.
Υπάρχει μια άλλη μέθοδος που αξίζει να αναφερθεί, κλώνος. Η μέθοδος του κλώνου κάνει το ίδιο πράγμα dup με μια σημαντική διάκριση: αναμένεται ότι τα αντικείμενα θα αντικαταστήσουν αυτή τη μέθοδο με μια που μπορεί να κάνει βαθιά αντίγραφα.
Έτσι στην πράξη τι σημαίνει αυτό; Σημαίνει ότι κάθε τάξη σας μπορεί να καθορίσει μια μέθοδο κλώνωσης που θα κάνει ένα βαθύ αντίγραφο αυτού του αντικειμένου. Σημαίνει επίσης ότι πρέπει να γράψετε μια μέθοδο κλώνου για κάθε κλάση που κάνετε.
Ένα κόλπο: Μάρσαλ
Το "Marshalling" ένα αντικείμενο είναι ένας άλλος τρόπος να λέμε "σειριοποίηση" ενός αντικειμένου. Με άλλα λόγια, μετατρέψτε αυτό το αντικείμενο σε μια ροή χαρακτήρων που μπορεί να γραφτεί σε ένα αρχείο που μπορείτε να "unmarshal" ή "unserialize" αργότερα για να αποκτήσετε το ίδιο αντικείμενο. Αυτό μπορεί να εκμεταλλευτεί για να πάρει ένα βαθύ αντίγραφο οποιουδήποτε αντικειμένου.
α = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
θέτει b.inspect
Τι συνέβη εδώ; Marshal.dump δημιουργεί μια "απόρριψη" του ενσωματωμένου πίνακα που είναι αποθηκευμένος στο ένα. Αυτή η απόρριψη είναι μια συμβολοσειρά δυαδικών χαρακτήρων που προορίζεται να αποθηκευτεί σε ένα αρχείο. Περιλαμβάνει το πλήρες περιεχόμενο του πίνακα, ένα πλήρες βαθύ αντίγραφο. Επόμενο, Marshal.load κάνει το αντίθετο. Αναλύει αυτόν τον πίνακα δυαδικών χαρακτήρων και δημιουργεί ένα εντελώς νέο πίνακα, με εντελώς νέα στοιχεία Array.
Αλλά αυτό είναι ένα τέχνασμα. Είναι αναποτελεσματική, δεν θα λειτουργήσει σε όλα τα αντικείμενα (τι συμβαίνει αν προσπαθήσετε να κλωνοποιήσετε μια σύνδεση δικτύου με αυτόν τον τρόπο;) και πιθανότατα δεν είναι τρομακτικά γρήγορη. Ωστόσο, είναι ο ευκολότερος τρόπος για να κάνετε βαθιά αντίγραφα των συνήθειας initialize_copy ή κλώνος μεθόδων. Επίσης, το ίδιο πράγμα μπορεί να γίνει με μεθόδους όπως to_yaml ή to_xml αν έχετε φορτώσει βιβλιοθήκες για να τις υποστηρίξετε.