В чем разница между методами Ruby dup и clone?


214

Документы Ruby дляdup говорят:

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

Но когда я сделал какой-то тест, я обнаружил, что они на самом деле одинаковы:

class Test
   attr_accessor :x
end

x = Test.new
x.x = 7
y = x.dup
z = x.clone
y.x => 7
z.x => 7

Так в чем же разница между этими двумя методами?


30
Хотелось бы, чтобы я знал не просто разницу в том, что dup и чтоclone делает, но почему бы использовать один, а не другой.
Эндрю Гримм

1
вот хорошая ссылка также - coderwall.com/p/1zflyg
Arup Rakshit

Ответы:


298

Подклассы могут переопределять эти методы для обеспечения различной семантики. В Objectсебе, есть два основных отличия.

Сначала cloneкопирует синглтон-класс, пока dupнет.

o = Object.new
def o.foo
  42
end

o.dup.foo   # raises NoMethodError
o.clone.foo # returns 42

Во-вторых, cloneсохраняет замороженное состояние, пока dupнет.

class Foo
  attr_accessor :bar
end
o = Foo.new
o.freeze

o.dup.bar = 10   # succeeds
o.clone.bar = 10 # raises RuntimeError

Реализация Rubinius для этих методов часто является моим источником ответов на эти вопросы, поскольку она достаточно ясна и является достаточно совместимой реализацией Ruby.


15
В случае, если кто-то попытается изменить это снова: «класс singleton», который является хорошо определенным термином в Ruby, включает в себя не только одноэлементные методы , но также и любые константы, определенные в одноэлементном классе. Рассмотрим: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Джереми Роман

3
отличный ответ, за которым последовал отличный комментарий, но это привело меня к погоне за диким гусем, чтобы понять этот синтаксис. это поможет всем, кто может быть сбит с толку: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Я думаю, что стоит упомянуть, что «синглтон-класс» включает в себя также любые модули, которые были extendотредактированы на исходном объекте. Так что Object.new.extend(Enumerable).dup.is_a?(Enumerable)возвращает ложь.
Даниил

Хотя этот ответ действительно отвечает на вопрос и утверждает различия. Также стоит отметить, что оба метода предназначены для разных ситуаций, как указано в документации Object # dup . Вариант использования для клонирования - клонирование объекта с намерением использовать его в качестве этого же экземпляра (с другим идентификатором объекта), тогда как dup предназначен для дублирования объекта в качестве базы для нового экземпляра.
3limin4t0r

189

При работе с ActiveRecord также есть существенная разница:

dup создает новый объект без его идентификатора, поэтому вы можете сохранить новый объект в базе данных, нажав .save

category2 = category.dup
#=> #<Category id: nil, name: "Favorites"> 

clone создает новый объект с тем же идентификатором, поэтому все изменения, внесенные в этот новый объект, будут перезаписывать исходную запись при нажатии .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">

43
В этом ответе ИМО имеет наиболее важную практическую информацию ... другие ответы касаются эзотерики, в то время как этот ответ указывает на критическое практическое различие.
jpw

37
Вышеуказанное относится только к ActiveRecord; Различие гораздо более тонкое в стандартном Ruby.
Ahmacleod

1
@Stefan и @jvalanen: Когда я подаю dupи cloneметоды на моем ActiveRecordобъекте, я получаю обратный результат того , что вы упомянули в ответе. что означает, что когда я использую dup, он создает новый объект с его idустановкой и при использовании cloneсоздает объект без его idустановки. Можете ли вы посмотреть на это еще раз и уточнить? , Thnx
huzefa biyawarwala

В Rails 5 тоже ничего не изменилось: api.rubyonrails.org/classes/ActiveRecord/… . Поэтому я верю, что в вашем случае есть что-то особенное ...
jvalanen

Тем не менее, cloneновая запись, которая никогда не была сохранена, должна быть довольно безопасной? Могу ли я создать «шаблонный объект» таким образом и клонировать его для сохранения конкретных экземпляров?
Кирилл Дюшон-Дорис

30

Одно из различий с замороженными предметами. cloneЗамороженного объекта также замораживают ( в то время как dupиз замороженного объекта не является).

class Test
  attr_accessor :x
end
x = Test.new
x.x = 7
x.freeze
y = x.dup
z = x.clone
y.x = 5 => 5
z.x = 5 => TypeError: can't modify frozen object

Другое отличие заключается в одноэлементных методах. Та же самая история здесь, dupне копирует те, но cloneделает.

def x.cool_method
  puts "Goodbye Space!"
end
y = x.dup
z = x.clone
y.cool_method => NoMethodError: undefined method `cool_method'
z.cool_method => Goodbye Space!

Это было очень полезно для меня. Если вы создаете замороженное постоянное значение и передаете его примерно так: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (обработка cookie Rails), вы можете легко получить ошибку когда они не знают вас, они клонируют его, а затем пытаются изменить клон. дублирование вашего замороженного значения и его передача позволяет вам, по крайней мере, гарантировать, что никто случайно не изменит вашу константу, не нарушая Rack здесь.
XP84

4

Оба почти идентичны, но клон делает еще одну вещь, чем dup. В клоне замороженное состояние объекта также копируется. В дупе, это всегда будет таять.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 

4

Более новый документ включает хороший пример:

class Klass
  attr_accessor :str
end

module Foo
  def foo; 'foo'; end
end

s1 = Klass.new #=> #<Klass:0x401b3a38>
s1.extend(Foo) #=> #<Klass:0x401b3a38>
s1.foo #=> "foo"

s2 = s1.clone #=> #<Klass:0x401b3a38>
s2.foo #=> "foo"

s3 = s1.dup #=> #<Klass:0x401b3a38>
s3.foo #=> NoMethodError: undefined method `foo' for #<Klass:0x401b3a38>

0

Вы можете использовать клон для программирования на прототипах в Ruby. Класс Object в Ruby определяет как метод clone, так и метод dup. И clone, и dup создают неглубокую копию копируемого объекта; то есть переменные экземпляра объекта копируются, но не объекты, на которые они ссылаются. Я продемонстрирую пример:

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
 => "red"
orange = apple.clone
orange.color 
 => "red"
orange.color << ' orange'
 => "red orange" 
apple.color
 => "red orange"

Обратите внимание, что в приведенном выше примере оранжевый клон копирует состояние (то есть переменные экземпляра) объекта apple, но там, где объект apple ссылается на другие объекты (например, цвет объекта String), эти ссылки не копируются. Вместо этого яблоко и апельсин оба ссылаются на один и тот же объект! В нашем примере ссылка на строковый объект red. Когда оранжевый использует метод добавления <<, чтобы изменить существующий объект String, он изменяет строковый объект на «красный оранжевый». По сути, это также меняет apple.color, поскольку они оба указывают на один и тот же объект String.

Как примечание: оператор присваивания = назначит новый объект и, таким образом, уничтожит ссылку. Вот демонстрация:

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'

В приведенном выше примере, когда мы присваиваем новый новый объект методу экземпляра цвета оранжевого клона, он больше не ссылается на тот же объект, что и яблоко. Следовательно, теперь мы можем изменить цветовой метод оранжевого цвета, не затрагивая цветовой метод apple, но если мы клонируем другой объект из apple, этот новый объект будет ссылаться на те же объекты в скопированных переменных экземпляра, что и на apple.

dup также создаст поверхностную копию объекта, который копирует, и если вы выполните ту же демонстрацию, показанную выше, для dup, вы увидите, что она работает точно так же. Но есть два основных различия между клоном и дупом. Во-первых, как уже упоминалось, клон копирует замороженное состояние, а dup - нет. Что это значит? Термин «замороженный» в Ruby - это эзотерический термин для неизменного, который сам по себе является номенклатурой в компьютерной науке, означая, что что-то нельзя изменить. Таким образом, замороженный объект в Ruby никак не может быть изменен; это, по сути, неизменным. Если вы попытаетесь изменить замороженный объект, Ruby вызовет исключение RuntimeError. Поскольку клон копирует замороженное состояние, если вы попытаетесь изменить клонированный объект, это вызовет исключение RuntimeError. И наоборот, поскольку dup не копирует замороженное состояние,

class Apple
  attr_accessor :color
  def initialize
    @color = 'red'
  end
end

apple = Apple.new
apple.frozen?
 => false 
apple.freeze
apple.frozen?
 => true 
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson' 
 => "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
 => false 
orange2 = apple.clone
orange2.frozen?
 => true 
orange.color = 'orange'
 => "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone

Во-вторых, и, что более интересно, клон копирует синглтон-класс (и, следовательно, его методы)! Это очень полезно, если вы хотите заняться программированием на основе прототипов в Ruby. Сначала давайте покажем, что действительно синглтон-методы копируются с помощью клона, а затем мы можем применить его на примере программирования на основе прототипов в Ruby.

class Fruit
  attr_accessor :origin
  def initialize
    @origin = :plant
  end
end

fruit = Fruit.new
 => #<Fruit:0x007fc9e2a49260 @origin=:plant> 
def fruit.seeded?
  true
end
2.4.1 :013 > fruit.singleton_methods
 => [:seeded?] 
apple = fruit.clone
 => #<Fruit:0x007fc9e2a19a10 @origin=:plant> 
apple.seeded?
 => true 

Как видите, синглтон-класс экземпляра объекта Fruit копируется в клон. И, следовательно, клонированный объект имеет доступ к одноэлементному методу: seeded ?. Но в случае с dup это не так:

apple = fruit.dup
 => #<Fruit:0x007fdafe0c6558 @origin=:plant> 
apple.seeded?
=> NoMethodError: undefined method `seeded?'

Теперь в программировании на основе прототипов у вас нет классов, которые расширяют другие классы, а затем создают экземпляры классов, чьи методы являются производными от родительского класса, который служит планом. Вместо этого у вас есть базовый объект, а затем вы создаете новый объект из объекта с копированием его методов и состояния (конечно, поскольку мы делаем мелкие копии с помощью клона, любые объекты, на которые ссылаются переменные экземпляра, будут совместно использоваться, как в JavaScript прототипы). Затем вы можете заполнить или изменить состояние объекта, заполнив детали клонированных методов. В приведенном ниже примере у нас есть базовый фруктовый объект. У всех фруктов есть семена, поэтому мы создаем метод number_of_seeds. Но у яблок есть одно семя, и поэтому мы создаем клон и заполняем детали. Теперь, когда мы клонируем яблоко, мы не только клонировали методы, но и клонировали государство! Помните, что клон делает поверхностную копию состояния (переменные экземпляра). И поэтому, когда мы клонируем яблоко, чтобы получить red_apple, у red_apple автоматически будет 1 семя! Вы можете думать о red_apple как об объекте, который наследуется от Apple, который, в свою очередь, наследуется от Fruit. Следовательно, именно поэтому я использовал заглавные буквы Fruit и Apple. Мы покончили с различием между классами и объектами благодаря клону.

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
 Apple = Fruit.clone
 => #<Object:0x007fb1d78165d8> 
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
 => #<Object:0x007fb1d892ac20 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

Конечно, у нас может быть метод конструктора в программировании на основе прототипов:

Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
  @number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
  @number_of_seeds
end
def Fruit.init(number_of_seeds)
  fruit_clone = clone
  fruit_clone.number_of_seeds = number_of_seeds
  fruit_clone
end
Apple = Fruit.init(1)
 => #<Object:0x007fcd2a137f78 @number_of_seeds=1> 
red_apple = Apple.clone
 => #<Object:0x007fcd2a1271c8 @number_of_seeds=1> 
red_apple.number_of_seeds
 => 1 

В конечном счете, используя клон, вы можете получить нечто похожее на поведение прототипа JavaScript.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.