Qual é a diferença entre os métodos dup e clone de Ruby?


214

Os documentos do Ruby paradup dizer:

Em geral, clonee duppode ter semântica diferente nas classes descendentes. Enquanto cloneé usado para duplicar um objeto, incluindo seu estado interno, dupnormalmente usa a classe do objeto descendente para criar a nova instância.

Mas quando faço alguns testes, descobri que eles são realmente os mesmos:

class Test
   attr_accessor :x
end

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

Então, quais são as diferenças entre os dois métodos?


29
Eu gostaria de saber não apenas a diferença entre o que dup e o queclone faz, mas por que você usaria um e não o outro.
Andrew Grimm

1
aqui está um bom link também - coderwall.com/p/1zflyg
Arup Rakshit

Respostas:


298

As subclasses podem substituir esses métodos para fornecer semânticas diferentes. Em Objectsi, existem duas diferenças principais.

Primeiro, clonecopia a classe singleton, enquanto dupnão.

o = Object.new
def o.foo
  42
end

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

Segundo, clonepreserva o estado congelado, enquanto dupnão.

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

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

A implementação do Rubinius para esses métodos geralmente é minha fonte de respostas para essas perguntas, pois é bastante clara e uma implementação do Ruby razoavelmente compatível.


15
Caso alguém tente mudar isso novamente: a "classe singleton", que é um termo bem definido no Ruby, inclui não apenas os métodos singleton , mas também quaisquer constantes definidas na classe singleton. Considere o seguinte: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
ótima resposta, seguida de um ótimo comentário, mas isso me levou a uma loucura para entender essa sintaxe. Isso vai ajudar alguém lá fora que também pode ser confundido: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Eu acho que vale a pena mencionar que a "classe singleton" inclui também quaisquer módulos que foram extendeditados no objeto original. Então Object.new.extend(Enumerable).dup.is_a?(Enumerable)retorna false.
Daniel Daniel

Embora essas respostas respondam à pergunta e afirmem as diferenças. Também é importante notar que ambos os métodos são destinados a diferentes situações, conforme declarado na documentação do objeto # dup . O caso de uso do clone está clonando um objeto com a intenção de usá-lo como a mesma instância (enquanto possui um ID de objeto diferente), enquanto o dup se destina a duplicar um objeto como base para uma nova instância.
3limin4t0r 13/10

189

Ao lidar com o ActiveRecord, também há uma diferença significativa:

dup cria um novo objeto sem que seu ID seja definido, para que você possa salvar um novo objeto no banco de dados pressionando .save

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

clone cria um novo objeto com o mesmo ID; portanto, todas as alterações feitas nesse novo objeto substituirão o registro original se você pressionar .save

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

43
Esta resposta é a que possui na IMO as informações práticas mais importantes ... as outras respostas se referem à esotérica, enquanto essa resposta identifica uma diferença prática crítica.
jpw

37
O acima é específico para ActiveRecord embora; a distinção é muito mais sutil no Ruby padrão.
ahmacleod

1
@ Stefan e @ jvalanen: Quando estou aplicando dupe clonemétodos no meu ActiveRecordobjeto, estou obtendo resultados inversos do que você mencionou na resposta. o que significa que quando estou usando dup, ele cria um novo objeto enquanto está idsendo definido e, enquanto o usa clone, cria um objeto sem que esteja idsendo definido. você pode procurar novamente e esclarecer? . Thnx
huzefa biyawarwala

Nada mudou no Rails 5: api.rubyonrails.org/classes/ActiveRecord/… . Então eu acredito que há algo especial no seu caso ...
jvalanen

No entanto, cloneum novo registro que nunca foi salvo deve ser bastante seguro, então? Posso criar um "objeto de modelo" dessa maneira e cloná-lo para salvar instâncias específicas?
Cyril Duchon-Doris

30

Uma diferença é com objetos congelados. O cloneobjeto congelado também está congelado (enquanto o dupobjeto congelado não está).

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

Outra diferença está nos métodos singleton. A mesma história aqui, dupnão as copia, mas clonesim.

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!

Isso foi muito útil para mim. Se você estiver criando um valor constante congelado e transmitindo-o para algo assim: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (manipulação de cookies do Rails), poderá obter facilmente um erro quando eles desconhecem você, eles o clonam e, em seguida, tentam modificar o clone. enganar seu valor congelado e repassá-lo permite pelo menos garantir que ninguém modifique acidentalmente sua constante, sem quebrar o Rack aqui.
XP84 14/07/16

4

Ambos são quase idênticos, mas o clone faz mais uma coisa que o dup. No clone, o estado congelado do objeto também é copiado. No dup, sempre será descongelado.

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

4

O documento mais recente inclui um bom exemplo:

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

Você pode usar o clone para fazer a programação baseada em protótipo no Ruby. A classe Object do Ruby define o método clone e o método dup. Tanto o clone quanto o dup produzem uma cópia superficial do objeto que está copiando; isto é, as variáveis ​​de instância do objeto são copiadas, mas não os objetos a que se referem. Vou demonstrar um exemplo:

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"

Observe no exemplo acima, o clone laranja copia o estado (ou seja, as variáveis ​​de instância) do objeto apple, mas onde o objeto apple faz referência a outros objetos (como a cor do objeto String), essas referências não são copiadas. Em vez disso, maçã e laranja fazem referência ao mesmo objeto! No nosso exemplo, a referência é o objeto de string 'red'. Quando orange usa o método append, <<, para modificar o objeto String existente, ele muda o objeto de string para 'laranja vermelho'. Isso também altera apple.color, pois ambos apontam para o mesmo objeto String.

Como observação lateral, o operador de atribuição, =, atribuirá um novo objeto e, assim, destruirá uma referência. Aqui está uma demonstração:

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'

No exemplo acima, quando atribuímos um novo objeto novo ao método de instância de cor do clone laranja, ele não faz mais referência ao mesmo objeto que a maçã. Portanto, agora podemos modificar o método de cor laranja sem afetar o método de cor da maçã, mas se clonarmos outro objeto da maçã, esse novo objeto fará referência aos mesmos objetos nas variáveis ​​de instância copiadas da maçã.

O dup também produzirá uma cópia superficial do objeto que está copiando e, se você fizer a mesma demonstração mostrada acima no dup, verá que ele funciona exatamente da mesma maneira. Mas existem duas grandes diferenças entre clone e dup. Primeiro, como outros mencionados, o clone copia o estado congelado e o dup não. O que isto significa? O termo 'congelado' em Ruby é um termo esotérico para imutável, que por si só é uma nomenclatura na ciência da computação, o que significa que algo não pode ser mudado. Portanto, um objeto congelado no Ruby não pode ser modificado de forma alguma; é, de fato, imutável. Se você tentar modificar um objeto congelado, o Ruby gerará uma exceção RuntimeError. Como o clone copia o estado congelado, se você tentar modificar um objeto clonado, ele gerará uma exceção RuntimeError. Por outro lado, como o dup não copia o estado congelado,

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

Segundo, e, mais interessante, o clone copia a classe singleton (e, portanto, seus métodos)! Isso é muito útil se você deseja realizar uma programação baseada em protótipo no Ruby. Primeiro, vamos mostrar que, de fato, os métodos singleton são copiados com o clone e, em seguida, podemos aplicá-lo em um exemplo de programação baseada em protótipo no 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 

Como você pode ver, a classe singleton da instância do objeto fruit é copiada para o clone. E, portanto, o objeto clonado tem acesso ao método singleton: seeded ?. Mas este não é o caso do dup:

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

Agora, na programação baseada em protótipo, você não possui classes que estendem outras classes e, em seguida, criam instâncias de classes cujos métodos derivam de uma classe pai que serve como um blueprint. Em vez disso, você tem um objeto base e, em seguida, cria um novo objeto com seus métodos e estado copiados (é claro, como estamos fazendo cópias rasas via clone, todos os objetos aos quais as variáveis ​​de instância referenciam serão compartilhados como no JavaScript protótipos). Você pode preencher ou alterar o estado do objeto preenchendo os detalhes dos métodos clonados. No exemplo abaixo, temos um objeto base de frutas. Como todas as frutas têm sementes, criamos um método number_of_seeds. Mas as maçãs têm uma semente e, por isso, criamos um clone e preenchemos os detalhes. Agora, quando clonamos a maçã, não apenas clonamos os métodos, mas clonamos o estado! Lembre-se de que o clone faz uma cópia superficial do estado (variáveis ​​de instância). E por isso, quando clonamos a maçã para obter uma maçã vermelha, a maçã vermelha automaticamente terá 1 semente! Você pode pensar em red_apple como um objeto que herda da Apple, que por sua vez herda de Fruit. Por isso, capitalizei Fruit e Apple. Acabamos com a distinção entre classes e objetos, cortesia do clone.

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 

Obviamente, podemos ter um método construtor em programação baseada em protoype:

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 

Por fim, usando o clone, você pode obter algo semelhante ao comportamento do protótipo JavaScript.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.