11.1.9. Копирование объектов

Встроенные методы Object#clone и #dup порождают копию вызывающего объекта. Различаются они объемом копируемого контекста. Метод #dup копирует только само содержимое объекта, тогда как clone сохраняет и такие вещи, как синглетные классы, ассоциированные с объектом.

s1 = 'cat'

def s1.upcase

 'CaT'

end

s1_dup = s1.dup

s1_clone = s1.clone

s1              #=> 'cat'

s1_dup.upcase   #=> 'CAT' (синглетный метод не копируется)

s1_clone.upcase #=> 'СаТ' (используется синглетный метод)

И dup, и clone выполняют поверхностное копирование, то есть копируют лишь содержимое самого вызывающего объекта. Если вызывающий объект содержит ссылки на другие объекты, то последние не копируются — копия будет ссылаться на те же самые объекты. Проиллюстрируем это на примере. Объект arr2 — копия arr1, поэтому изменение элемента целиком, например arr2[2], не оказывает влияния на arr1. Но исходный массив и его копия содержат ссылку на один и тот же объект String, поэтому изменение строки через arr2 приведет к такому же изменению значения, на которое ссылается arr1.

arr1 = [ 1, 'flipper', 3 ]

arr2 = arr1.dup

arr2[2] = 99

arr2[1][2] = 'a'

arr1 # [1, 'flapper', 3]

arr2 # [1, 'flapper', 99]

Иногда необходимо глубокое копирование, при котором копируется все дерево объектов с корнем в исходном объекте. В этом случае между оригиналом и копией гарантированно не будет никакой интерференции. Ruby не предоставляет встроенного метода для глубокого копирования, но есть приемы, позволяющие достичь желаемого результата.

Самый «чистый» способ — потребовать, чтобы классы реализовывали метод deep_copy. Он мог бы рекурсивно обходить все объекты, на которые ссылается исходный объект, и вызывать для них метод deep_copy. Необходимо было бы еще добавить метод deep_copy во все встроенные классы Ruby, которыми вы пользуетесь.

Но есть и более быстрый способ с использованием модуля Marshal. Если вы сериализуете исходный объект, представив его в виде строки, а затем загрузите в новый объект, то этот новый объект будет копией исходного.

arr1 = [ 1, 'flipper', 3 ]

arr2 = Marshal.load(Marshal.dump(arr1))

arr2[2] = 99

arr2[1][2] = 'a'

arr1 # [1, 'flipper', 3]

arr2 # [1, 'flapper', 99]

Обратите внимание, что изменение строки через arr2 не отразилось на строке, на которую ссылается arr1.

11.1.10. Метод initialize_copy

При копировании объекта методом dup или clone конструктор не вызывается. Копируется вся информация о состоянии.

Но что делать, если вам такое поведение не нужно? Рассмотрим пример:

class Document

 attr_accessor :title, :text

 attr_reader :timestamp

 def initialize(title, text)

  @title, @text = title, text

  @timestamp = Time.now

 end

end

doc1 = Document.new('Random Stuff',File.read('somefile'))

sleep 300 # Немного подождем...

doc2 = doc1.clone

doc1.timestamp == doc2.timestamp # true

# Оп... временные штампы одинаковы!

При создании объекта Document с ним ассоциируется временной штамп. При копировании объекта копируется и его временной штамп. А как быть, если мы хотим запомнить время, когда было выполнено копирование?

Для этого нужно определить метод initialize_copy. Он вызывается как раз при копировании объекта. Этот метод аналогичен initialize и позволяет полностью контролировать состояние объекта.

class Document # Определяем новый метод в классе.

 def initialize_copy(other)

  @timestamp = Time.now

 end

end

doc3 = Document.new('More Stuff', File.read('otherfile'))

sleep 300                        # Немного подождем...

doc4 = doc3.clone

doc3.timestamp == doc4.timestamp # false

# Теперь временные штампы правильны.

Отметим, что метод initialize_copy вызывается после того, как вся информация скопирована. Поэтому мы и опустили строку:

@title, @text = other.title, other.text

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату