Вероятно, причина, по которой Ruby не содержит глубокого клона, связана со сложностью проблемы. Смотрите примечания в конце.
Чтобы создать клон, который будет «глубоко копировать», хэши, массивы и элементарные значения, т. Е. Сделать копию каждого элемента в оригинале так, чтобы копия имела те же значения, но новые объекты, которые вы можете использовать это:
class Object
def deepclone
case
when self.class==Hash
hash = {}
self.each { |k,v| hash[k] = v.deepclone }
hash
when self.class==Array
array = []
self.each { |v| array << v.deepclone }
array
else
if defined?(self.class.new)
self.class.new(self)
else
self
end
end
end
end
Если вы хотите переопределить поведение метода clone
в Ruby, вы можете назвать его просто clone
вместо deepclone
(в 3 местах), но я понятия не имею, как переопределение поведения клонов в Ruby повлияет на библиотеки Ruby. или Ruby on Rails, так что будьте бдительны. Лично я не могу рекомендовать это делать.
Например:
a = {'a'=>'x','b'=>'y'} => {"a"=>"x", "b"=>"y"}
b = a.deepclone => {"a"=>"x", "b"=>"y"}
puts "#{a['a'].object_id} / #{b['a'].object_id}" => 15227640 / 15209520
Если вы хотите, чтобы ваши классы правильно клонировали должным образом, их метод new
(initialize) должен быть способен к глубокому клонированию объекта этого класса стандартным способом, т. Е. Если задан первый параметр, предполагается, что это объект для глубокого клонирования.
Предположим, нам нужен класс M, например. Первый параметр должен быть необязательным объектом класса M. Здесь у нас есть второй необязательный аргумент z
для предварительной установки значения z в новом объекте.
class M
attr_accessor :z
def initialize(m=nil, z=nil)
if m
# deepclone all the variables in m to the new object
@z = m.z.deepclone
else
# default all the variables in M
@z = z # default is nil if not specified
end
end
end
Предварительно установленный z
здесь игнорируется при клонировании, но ваш метод может иметь другое поведение. Объекты этого класса будут созданы следующим образом:
# a new 'plain vanilla' object of M
m=M.new => #<M:0x0000000213fd88 @z=nil>
# a new object of M with m.z pre-set to 'g'
m=M.new(nil,'g') => #<M:0x00000002134ca8 @z="g">
# a deepclone of m in which the strings are the same value, but different objects
n=m.deepclone => #<M:0x00000002131d00 @z="g">
puts "#{m.z.object_id} / #{n.z.object_id}" => 17409660 / 17403500
Где объекты класса M являются частью массива:
a = {'a'=>M.new(nil,'g'),'b'=>'y'} => {"a"=>#<M:0x00000001f8bf78 @z="g">, "b"=>"y"}
b = a.deepclone => {"a"=>#<M:0x00000001766f28 @z="g">, "b"=>"y"}
puts "#{a['a'].object_id} / #{b['a'].object_id}" => 12303600 / 12269460
puts "#{a['b'].object_id} / #{b['b'].object_id}" => 16811400 / 17802280
Примечания:
- Если
deepclone
попытается клонировать объект, который не клонирует себя стандартным способом, он может потерпеть неудачу.
- Если
deepclone
пытается клонировать объект, который может клонировать себя стандартным способом, и если это сложная структура, он может (и, вероятно, будет) делать мелкий клон самого себя.
deepclone
не копирует ключи в Хэше. Причина в том, что они обычно не обрабатываются как данные, но если вы измените hash[k]
на hash[k.deepclone]
, они также будут глубоко скопированы.
- Некоторые элементарные значения не имеют метода
new
, например Fixnum. Эти объекты всегда имеют одинаковый идентификатор объекта и копируются, а не клонируются.
- Будьте осторожны, потому что при глубоком копировании две части вашего хэша или массива, которые содержали один и тот же объект в оригинале, будут содержать разные объекты в глубоком клоне.