Часто необходимо сделать копию значение в рубине. Хотя это может показаться простым, и это для простых объектов, как только вы должны сделать копию данных структура с несколькими массивами или хэшами на одном объекте, вы быстро найдете много подводные камни.
Объекты и ссылки
Чтобы понять, что происходит, давайте рассмотрим простой код. Во-первых, оператор присваивания, использующий тип POD (Plain Old Data) в Рубин.
а = 1
б = а
а + = 1
ставит б
Здесь оператор присваивания копирует значение и назначив его б используя оператор присваивания. Любые изменения в не будет отражено в б. Но как насчет чего-то более сложного? Учти это.
а = [1,2]
б = а
<< 3
ставит
Перед запуском вышеуказанной программы попытайтесь угадать, какой будет выход и почему. Это не то же самое, что в предыдущем примере, изменения, внесенные в отражены в б, но почему? Это потому что массив объект не является типом POD. Оператор присваивания не создает копию значения, он просто копирует ссылка к объекту Array. и б переменные сейчас Ссылки для того же объекта Array любые изменения в одной переменной будут видны в другой.
И теперь вы можете понять, почему копирование нетривиальных объектов со ссылками на другие объекты может быть сложным. Если вы просто делаете копию объекта, вы просто копируете ссылки на более глубокие объекты, поэтому ваша копия называется «мелкой копией».
Что обеспечивает Ruby: dup и clone
Ruby предоставляет два метода для создания копий объектов, в том числе один, который можно сделать для создания глубоких копий. Объект # DUP Метод сделает поверхностную копию объекта. Чтобы достичь этого, дубликат метод вызовет initialize_copy метод этого класса. Что именно это делает, зависит от класса. В некоторых классах, таких как Array, он инициализирует новый массив с теми же членами, что и исходный массив. Это, однако, не глубокая копия. Учтите следующее.
а = [1,2]
b = a.dup
<< 3
ставит
a = [[1,2]]
b = a.dup
а [0] << 3
ставит
Что здесь произошло? Массив # initialize_copy Метод действительно создаст копию массива, но эта копия сама по себе является мелкой копией. Если у вас есть какие-либо другие не POD типы в вашем массиве, используя дубликат будет только частично глубокая копия. Это будет так же глубоко, как первый массив, глубже массивы, хэши или другие объекты будут копироваться только мелко.
Есть еще один метод, который стоит упомянуть, клон. Метод клонирования делает то же самое, что и метод дубликат с одним важным отличием: ожидается, что объекты переопределят этот метод с тем, который может делать глубокие копии.
Так на практике, что это значит? Это означает, что каждый из ваших классов может определить метод-клон, который сделает глубокую копию этого объекта. Это также означает, что вы должны написать метод клонирования для каждого создаваемого вами класса.
Трюк: Маршаллинг
«Выделение» объекта - это еще один способ сказать «сериализацию» объекта. Другими словами, превратите этот объект в символьный поток, который можно записать в файл, который вы можете позже «разархивировать» или «десериализовать», чтобы получить тот же объект. Это может быть использовано для получения глубокой копии любого объекта.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
а [0] << 3
ставит
Что здесь произошло? Marshal.dump создает «дамп» вложенного массива, хранящегося в . Этот дамп представляет собой двоичную строку символов, предназначенную для хранения в файле. Он содержит полное содержимое массива, полную глубокую копию. Следующий, Marshal.load делает обратное. Он анализирует этот двоичный массив символов и создает совершенно новый массив Array с совершенно новыми элементами Array.
Но это трюк. Это неэффективно, оно не будет работать на всех объектах (что произойдет, если вы попытаетесь клонировать сетевое соединение таким образом?) И, вероятно, не очень быстро. Тем не менее, это самый простой способ сделать глубокие копии за исключением пользовательских initialize_copy или клон методы. Кроме того, то же самое можно сделать с помощью таких методов, как to_yaml или to_xml если у вас есть загруженные библиотеки для их поддержки.