¿Cuál es la diferencia entre los métodos de duplicación y clonación de Ruby?


214

Los documentos de Ruby paradup decir:

En general, cloney duppuede tener una semántica diferente en las clases descendientes. Mientras clonese usa para duplicar un objeto, incluido su estado interno, dupgeneralmente usa la clase del objeto descendiente para crear la nueva instancia.

Pero cuando hago alguna prueba descubrí que en realidad son lo mismo:

class Test
   attr_accessor :x
end

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

Entonces, ¿cuáles son las diferencias entre los dos métodos?


29
Ojalá supiera no solo la diferencia en qué dup y quéclone hace, sino por qué usaría uno en lugar del otro.
Andrew Grimm

1
aquí hay un buen enlace también - coderwall.com/p/1zflyg
Arup Rakshit

Respuestas:


298

Las subclases pueden anular estos métodos para proporcionar una semántica diferente. En Objectsí mismo, hay dos diferencias clave.

Primero, clonecopia la clase singleton, mientras dupque no.

o = Object.new
def o.foo
  42
end

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

En segundo lugar, cloneconserva el estado congelado, mientras dupque no.

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

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

La implementación de Rubinius para estos métodos es a menudo mi fuente de respuestas a estas preguntas, ya que es bastante clara y una implementación de Ruby bastante compatible.


15
En caso de que alguien intente cambiar esto nuevamente: la "clase singleton", que es un término bien definido en Ruby, incluye no solo los métodos singleton , sino también las constantes definidas en la clase singleton. Considere lo siguiente: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
gran respuesta, seguida de un gran comentario, pero me llevó a una loca búsqueda para entender esa sintaxis. esto ayudará a cualquier otra persona que también pueda estar confundida: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Creo que vale la pena mencionar que la "clase singleton" incluye también cualquier módulo que se haya extendeditado en el objeto original. Entonces Object.new.extend(Enumerable).dup.is_a?(Enumerable)devuelve falso.
Daniel

Aunque esta respuesta responde la pregunta y establece las diferencias. También vale la pena señalar que ambos métodos están destinados a diferentes situaciones, como se indica en la documentación Object # dup . El caso de uso para clonar es clonar un objeto con la intención de usarlo como esa misma instancia (mientras que tiene una identificación de objeto diferente), mientras que dup está destinado a duplicar un objeto como base para una nueva instancia.
3limin4t0r

189

Cuando se trata de ActiveRecord también hay una diferencia significativa:

dup crea un nuevo objeto sin que se establezca su ID, por lo que puede guardar un nuevo objeto en la base de datos presionando .save

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

clone crea un nuevo objeto con la misma identificación, por lo que todos los cambios realizados en ese nuevo objeto sobrescribirán el registro original si se presiona .save

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

43
ESTA respuesta es la que tiene IMO la información práctica más importante ... las otras respuestas se centran en esotérica, mientras que esta respuesta señala una diferencia práctica crítica.
jpw

37
Sin embargo, lo anterior es específico de ActiveRecord; La distinción es mucho más sutil en Ruby estándar.
ahmacleod

1
@Stefan y @jvalanen: cuando estoy aplicando dupy clonemétodos en mi ActiveRecordobjeto, obtengo resultados inversos de lo que has mencionado en la respuesta. lo que significa que cuando estoy usando dup, crea un nuevo objeto con el que se idestá configurando y mientras lo usa clonecrea un objeto sin que idse configure. ¿Puedes por favor mirarlo nuevamente y aclararlo? . Thnx
huzefa biyawarwala 03 de

Nada ha cambiado en Rails 5 tampoco: api.rubyonrails.org/classes/ActiveRecord/… . Así que creo que hay algo especial en su caso ...
jvalanen

Sin embargo, ¿ clonecrear un nuevo registro que nunca se haya guardado debería ser bastante seguro? ¿Puedo construir un "objeto de plantilla" de esta manera y clonarlo para guardar instancias específicas?
Cyril Duchon-Doris

30

Una diferencia es con los objetos congelados. El clonede un objeto congelado también está congelado (mientras que el dupde un objeto congelado no lo 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

Otra diferencia es con los métodos singleton. La misma historia aquí, dupno copia esos, pero clonesí.

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!

Esto fue muy útil para mí. Si está creando un valor constante congelado y lo pasa a algo como esto: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (manejo de cookies de Rails), puede obtener fácilmente un error cuando no lo saben, lo clonan y luego intentan modificarlo. duplicar su valor congelado y pasarlo le permite al menos garantizar que nadie modifique accidentalmente su constante, sin romper Rack aquí.
XP84

4

Ambos son casi idénticos, pero el clon hace una cosa más que dup. En clon, el estado congelado del objeto también se copia. En dup, siempre estará descongelado.

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

4

El documento más reciente incluye un buen ejemplo:

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

Puedes usar clone para hacer programación basada en prototipos en Ruby. La clase Object de Ruby define tanto el método de clonación como el método dup. Tanto clone como dup producen una copia superficial del objeto que está copiando; es decir, las variables de instancia del objeto se copian pero no los objetos a los que hacen referencia. Voy a demostrar un ejemplo:

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 que en el ejemplo anterior, el clon naranja copia el estado (es decir, las variables de instancia) del objeto apple, pero cuando el objeto apple hace referencia a otros objetos (como el color del objeto String), esas referencias no se copian. En cambio, ¡manzana y naranja hacen referencia al mismo objeto! En nuestro ejemplo, la referencia es el objeto de cadena 'rojo'. Cuando orange usa el método append, <<, para modificar el objeto String existente, cambia el objeto string a 'red orange'. En efecto, esto también cambia apple.color, ya que ambos apuntan al mismo objeto String.

Como nota al margen, el operador de asignación, =, asignará un nuevo objeto y, por lo tanto, destruirá una referencia. Aquí hay una demostración:

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'

En el ejemplo anterior, cuando asignamos un nuevo objeto nuevo al método de instancia de color del clon naranja, ya no hace referencia al mismo objeto que la manzana. Por lo tanto, ahora podemos modificar el método de color naranja sin afectar el método de color de apple, pero si clonamos otro objeto de apple, ese nuevo objeto hará referencia a los mismos objetos en las variables de instancia copiadas como apple.

dup también producirá una copia superficial del objeto que está copiando, y si hiciera la misma demostración que se muestra arriba a dup, verá que funciona exactamente de la misma manera. Pero hay dos diferencias principales entre clonar y dup. Primero, como otros mencionaron, clone copia el estado congelado y dup no. ¿Qué significa esto? El término 'congelado' en Ruby es un término esotérico para inmutable, que en sí mismo es una nomenclatura en informática, lo que significa que algo no puede cambiarse. Por lo tanto, un objeto congelado en Ruby no puede modificarse de ninguna manera; es, en efecto, inmutable. Si intenta modificar un objeto congelado, Ruby generará una excepción RuntimeError. Dado que clonar copia el estado congelado, si intenta modificar un objeto clonado, generará una excepción RuntimeError. Por el contrario, dado que dup no copia el 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

En segundo lugar, y, más interesante, ¡el clon copia la clase singleton (y por lo tanto sus métodos)! Esto es muy útil si desea realizar una programación basada en prototipos en Ruby. Primero, demostremos que, de hecho, los métodos singleton se copian con clon, y luego podemos aplicarlo en un ejemplo de programación basada en prototipos en 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 puede ver, la clase singleton de la instancia del objeto de fruta se copia en el clon. Y, por lo tanto, el objeto clonado tiene acceso al método singleton: ¿sembrado ?. Pero este no es el caso con dup:

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

Ahora, en la programación basada en prototipos, no tiene clases que extiendan otras clases y luego cree instancias de clases cuyos métodos se derivan de una clase principal que sirve como modelo. En cambio, tiene un objeto base y luego crea un nuevo objeto a partir del objeto con sus métodos y estado copiados (por supuesto, dado que estamos haciendo copias superficiales a través de clon, cualquier objeto al que se haga referencia de variables de instancia se compartirá tal como en JavaScript prototipos). Luego puede completar o cambiar el estado del objeto completando los detalles de los métodos clonados. En el siguiente ejemplo, tenemos un objeto de fruta base. Todas las frutas tienen semillas, por lo que creamos un método number_of_seeds. Pero las manzanas tienen una semilla, por lo que creamos un clon y completamos los detalles. Ahora cuando clonamos manzana, ¡no solo clonamos los métodos sino que clonamos el estado! Recuerde que el clon hace una copia superficial del estado (variables de instancia). Y por eso, cuando clonemos una manzana para obtener una manzana roja, ¡la manzana roja automáticamente tendrá 1 semilla! Puedes pensar en red_apple como un objeto que hereda de Apple, que a su vez hereda de Fruit. Por eso, capitalicé Fruit and Apple. Eliminamos la distinción entre clases y objetos por cortesía del clon.

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 

Por supuesto, podemos tener un método constructor en la programación basada en prototipos:

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 

En última instancia, utilizando clone, puede obtener algo similar al comportamiento del prototipo de JavaScript.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.