Често е необходимо да се направи копие на стойност в Ruby. Въпреки че това може да изглежда просто и е за прости обекти, веднага щом трябва да направите копие на данни структура с множество масиви или хеши на един и същ обект, бързо ще откриете, че има много клопки.
Обекти и справки
За да разберем какво се случва, нека разгледаме някакъв прост код. Първо, операторът за присвояване, използващ POD (Plain Old Data) в рубин.
a = 1
b = a
a + = 1
поставя б
Тук операторът за присвояване прави копие на стойността на а и възлагането му на б използвайки оператора за присвояване. Всякакви промени в а няма да се отрази в б. Но какво да кажем за нещо по-сложно? Обмислете това.
a = [1,2]
b = a
a << 3
поставя b.inspect
Преди да стартирате горната програма, опитайте се да отгатнете какъв ще бъде изходът и защо. Това не е същото като предишния пример, направени промени а са отразени в б, но защо? Това е така, защото Array обектът не е тип POD. Операторът за присвояване не прави копие на стойността, а просто копира
препратка към обекта Array. Най- а и б променливите са сега препратки към същия обект Array, всички промени в която и да е променлива ще се видят в другата.И сега можете да видите защо копирането на нетривиални обекти с препратки към други обекти може да бъде сложно. Ако просто направите копие на обекта, вие просто копирате препратките към по-дълбоките обекти, така че вашето копие се нарича "плитко копие".
Какво предоставя Ruby: дуп и клониране
Ruby предлага два метода за създаване на копия на обекти, включително един, който може да бъде направен в дълбоки копия. Най- Обект # мезонети метод ще направи плитко копие на обект. За да постигне това, мезонети, метод ще извика initialize_copy метод от този клас. Какво точно прави това, зависи от класа. В някои класове, като Array, той ще инициализира нов масив със същите членове като оригиналния масив. Това обаче не е дълбоко копие. Обмислете следното.
a = [1,2]
b = a.dup
a << 3
поставя b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
поставя b.inspect
Какво се е случило тук? Най- Array # initialize_copy метод наистина ще направи копие на масив, но това копие само по себе си е плитко копие. Ако имате други видове, които не са под POD, в масива си, като използвате мезонети, ще бъде само частично дълбоко копие. Той ще бъде само толкова дълбок като първия масив, който и да е по-дълбок масиви, хешове или други обекти ще бъдат копирани само плитко.
Има още един метод, който си струва да се спомене, клонинг. Методът на клониране прави същото като мезонети, с едно важно разграничение: очаква се обектите да заменят този метод с такъв, който може да прави дълбоки копия.
Така че на практика какво означава това? Това означава, че всеки от вашите класове може да определи метод на клониране, който ще направи дълбоко копие на този обект. Това също означава, че трябва да напишете метод за клониране за всеки клас, който правите.
Трик: Маршируване
„Маршалиране“ на обект е друг начин да се каже „сериализиране“ на обект. С други думи, превърнете този обект в символен поток, който може да бъде записан във файл, който можете да „демарширате“ или „несериализирате“ по-късно, за да получите същия обект. Това може да се използва за получаване на дълбоко копие на всеки обект.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
поставя b.inspect
Какво се е случило тук? Marshal.dump създава "изхвърляне" на вложен масив, съхраняван в а. Този dump е низ от двоични символи, предназначен да се съхранява във файл. В него се съхранява пълното съдържание на масива, пълно дълбоко копие. Следващия, Marshal.load прави обратното. Той анализира този масив от двоични символи и създава напълно нов масив, с напълно нови елементи от масива.
Но това е трик. Той е неефективен, няма да работи на всички обекти (какво се случва, ако се опитате да клонирате мрежова връзка по този начин?) И вероятно не е ужасно бърз. Въпреки това, това е най-лесният начин да се направят дълбоки копия, които не са по поръчка initialize_copy или клонинг методи. Също така, същото може да се направи и с методи като to_yaml или to_xml ако имате заредени библиотеки, които да ги поддържат.