Qual è la differenza tra i metodi dupy e clone di Ruby?


214

I documenti di Ruby perdup dire:

In generale, clonee duppuò avere una semantica diversa nelle classi discendenti. Mentre cloneviene utilizzato per duplicare un oggetto, incluso il suo stato interno, in dupgenere utilizza la classe dell'oggetto discendente per creare la nuova istanza.

Ma quando faccio qualche test ho scoperto che in realtà sono gli stessi:

class Test
   attr_accessor :x
end

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

Quindi quali sono le differenze tra i due metodi?


30
Vorrei non sapere semplicemente la differenza in cosa dup e cosaclone fa, ma perché dovresti usare l'uno piuttosto che l'altro.
Andrew Grimm,

1
ecco un buon collegamento anche - coderwall.com/p/1zflyg
Arup Rakshit

Risposte:


298

Le sottoclassi possono ignorare questi metodi per fornire una semantica diversa. Di per Objectsé, ci sono due differenze chiave.

Innanzitutto, clonecopia la classe singleton, mentre dupnon lo fa.

o = Object.new
def o.foo
  42
end

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

In secondo luogo, cloneconserva lo stato congelato, mentre dupnon lo fa.

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

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

L' implementazione di Rubinius per questi metodi è spesso la mia fonte di risposte a queste domande, poiché è abbastanza chiara e un'implementazione di Ruby abbastanza conforme.


15
Nel caso in cui qualcuno tenti di modificarlo di nuovo: la "classe singleton", che è un termine ben definito in Ruby, include non solo i metodi singleton , ma anche tutte le costanti definite nella classe singleton. Prendere in considerazione: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman,

3
ottima risposta, seguita da un grande commento, ma mi ha portato a una caccia all'oca selvaggia per capire quella sintassi. questo aiuterà chiunque altro là fuori che potrebbe anche essere confuso: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Penso che valga la pena ricordare che la "classe singleton" include anche tutti i moduli che sono stati modificati extendsull'oggetto originale. Quindi Object.new.extend(Enumerable).dup.is_a?(Enumerable)restituisce false.
Daniel,

Anche se questa risposta risponde alla domanda e indica le differenze. Vale anche la pena notare che entrambi i metodi sono pensati per situazioni diverse come indicato dall'oggetto # dup documentazione . Il caso d'uso per il clone è la clonazione di un oggetto con l'intenzione di usarlo come quella stessa istanza (pur avendo un ID oggetto diverso), mentre dup ha lo scopo di duplicare un oggetto come base per una nuova istanza.
3limin4t0r,

189

Quando si ha a che fare con ActiveRecord c'è anche una differenza significativa:

dup crea un nuovo oggetto senza che sia impostato il suo ID, quindi è possibile salvare un nuovo oggetto nel database premendo .save

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

clone crea un nuovo oggetto con lo stesso ID, quindi tutte le modifiche apportate a quel nuovo oggetto sovrascriveranno il record originale se colpiscono .save

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

43
QUESTA risposta è quella che ha l'IMO le informazioni pratiche più importanti ... le altre risposte si soffermano sull'esoterica, mentre questa risposta individua una differenza pratica critica.
jpw,

37
Quanto sopra è specifico di ActiveRecord; la distinzione è molto più sottile nello standard Ruby.
ahmacleod,

1
@Stefan e @jvalanen: quando sto applicando dupe clonemetodi sul mio ActiveRecordoggetto, sto ottenendo risultati inversi rispetto a ciò che hai menzionato nella risposta. il che significa che quando lo sto usando dup, crea un nuovo oggetto idmentre viene impostato e mentre lo utilizza clonecrea un oggetto senza che idsia impostato. puoi per favore guardarci di nuovo e chiarire? . Thnx
huzefa biyawarwala

Nulla è cambiato in Rails 5: api.rubyonrails.org/classes/ActiveRecord/… . Quindi credo che ci sia qualcosa di speciale nel tuo caso ...
jvalanen,

Tuttavia, cloneun nuovo record che non è mai stato salvato dovrebbe essere abbastanza sicuro allora? Posso creare un "oggetto modello" in questo modo e clonarlo per salvare istanze specifiche?
Cyril Duchon-Doris,

30

Una differenza è con gli oggetti congelati. Anche clonedi un oggetto congelato viene congelato (mentre dupnon lo è un oggetto congelato).

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

Un'altra differenza è con i metodi singleton. Stessa storia qui, dupnon copia quelli, ma lo clonefa.

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!

Questo mi è stato molto utile. Se stai creando un valore costante congelato e lo passi a qualcosa del genere: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (gestione dei cookie di Rails), puoi facilmente ottenere un errore quando a tua insaputa lo clonano e quindi tentano di modificare il clone. duplicare il tuo valore congelato e passarlo in ti consente almeno di garantire che nessuno modifichi accidentalmente la tua costante, senza rompere Rack qui.
XP84,

4

Entrambi sono quasi identici ma il clone fa una cosa in più di dup. In clone, viene copiato anche lo stato congelato dell'oggetto. In duplex, sarà sempre scongelato.

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

4

Il documento più recente include un buon esempio:

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

È possibile utilizzare il clone per eseguire la programmazione basata su prototipi in Ruby. La classe Object di Ruby definisce sia il metodo clone che il metodo dup. Sia clone che dup producono una copia superficiale dell'oggetto che sta copiando; cioè, le variabili di istanza dell'oggetto vengono copiate ma non gli oggetti a cui fanno riferimento. Dimostrerò un esempio:

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"

Si noti nell'esempio precedente, il clone arancione copia lo stato (ovvero le variabili di istanza) dell'oggetto apple, ma dove l'oggetto apple fa riferimento ad altri oggetti (come il colore dell'oggetto String), tali riferimenti non vengono copiati. Invece, mela e arancia fanno entrambi riferimento allo stesso oggetto! Nel nostro esempio, il riferimento è l'oggetto stringa 'rosso'. Quando orange usa il metodo append, <<, per modificare l'oggetto String esistente, cambia l'oggetto stringa in 'red orange'. Questo in effetti cambia anche apple.color, poiché entrambi puntano allo stesso oggetto String.

Come nota a margine, l'operatore di assegnazione, =, assegnerà un nuovo oggetto e quindi distruggerà un riferimento. Ecco una dimostrazione:

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'

Nell'esempio sopra, quando abbiamo assegnato un nuovo oggetto nuovo al metodo dell'istanza di colore del clone arancione, non fa più riferimento allo stesso oggetto di Apple. Quindi, ora possiamo modificare il metodo di colore arancione senza influire sul metodo di colore di apple, ma se cloniamo un altro oggetto da apple, quel nuovo oggetto farà riferimento agli stessi oggetti nelle variabili di istanza copiate di apple.

dup produrrà anche una copia superficiale dell'oggetto che sta copiando, e se dovessi fare la stessa dimostrazione mostrata sopra su dup, vedrai che funziona esattamente allo stesso modo. Ma ci sono due principali differenze tra clone e dup. In primo luogo, come altri hanno già detto, il clone copia lo stato congelato e dup non lo fa. Cosa significa questo? Il termine "congelato" in Ruby è un termine esoterico per immutabile, che a sua volta è una nomenclatura nell'informatica, il che significa che qualcosa non può essere cambiato. Pertanto, un oggetto congelato in Ruby non può essere modificato in alcun modo; è, in effetti, immutabile. Se si tenta di modificare un oggetto congelato, Ruby genererà un'eccezione RuntimeError. Poiché il clone copia lo stato congelato, se si tenta di modificare un oggetto clonato, genererà un'eccezione RuntimeError. Al contrario, poiché dup non copia lo stato bloccato,

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

In secondo luogo, e, più interessante, il clone copia la classe singleton (e quindi i suoi metodi)! Questo è molto utile se desideri intraprendere una programmazione basata su prototipi in Ruby. Innanzitutto, mostriamo che in effetti i metodi singleton vengono copiati con il clone e quindi possiamo applicarlo in un esempio di programmazione basata su prototipi in 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 

Come puoi vedere, la classe singleton dell'istanza dell'oggetto fruit viene copiata nel clone. E quindi l'oggetto clonato ha accesso al metodo singleton: seeded ?. Ma questo non è il caso di dup:

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

Ora nella programmazione basata su prototipi, non esistono classi che estendono altre classi e quindi creano istanze di classi i cui metodi derivano da una classe genitore che funge da modello. Invece, hai un oggetto base e quindi crei un nuovo oggetto dall'oggetto con i suoi metodi e lo stato copiato (ovviamente, poiché stiamo facendo copie superficiali tramite clone, tutti gli oggetti a cui fanno riferimento le variabili di istanza saranno condivisi proprio come in JavaScript prototipi). È quindi possibile compilare o modificare lo stato dell'oggetto compilando i dettagli dei metodi clonati. Nell'esempio seguente, abbiamo un oggetto frutto di base. Tutti i frutti hanno semi, quindi creiamo un metodo number_of_seeds. Ma le mele hanno un seme, quindi creiamo un clone e riempiamo i dettagli. Ora, quando cloniamo la mela, non solo abbiamo clonato i metodi ma abbiamo clonato lo stato! Ricorda che il clone esegue una copia superficiale dello stato (variabili di istanza). E per questo motivo, quando cloniamo apple per ottenere un red_apple, red_apple avrà automaticamente 1 seme! Puoi pensare a red_apple come un oggetto che eredita da Apple, che a sua volta eredita da Fruit. Quindi, ecco perché ho capitalizzato Fruit and Apple. Abbiamo eliminato la distinzione tra classi e oggetti per gentile concessione del 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 

Naturalmente, possiamo avere un metodo di costruzione nella programmazione basata su prototipi:

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 

Alla fine, usando clone, puoi ottenere qualcosa di simile al comportamento del prototipo JavaScript.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.