Was ist der Unterschied zwischen Rubys Dup- und Klonmethoden?


214

Die Ruby-Dokumentedup sagen zum Beispiel:

Im Allgemeinen cloneund dupkann unterschiedliche Semantik in absteigenden Klassen haben. Während clonezum Duplizieren eines Objekts einschließlich seines internen Status verwendet wird, wird dupnormalerweise die Klasse des untergeordneten Objekts zum Erstellen der neuen Instanz verwendet.

Aber als ich einen Test machte, stellte ich fest, dass sie tatsächlich gleich sind:

class Test
   attr_accessor :x
end

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

Was sind also die Unterschiede zwischen den beiden Methoden?


29
Ich wünschte, ich wüsste nicht nur den Unterschied in dem, was dup und wasclone , sondern auch, warum Sie das eine anstelle des anderen verwenden würden.
Andrew Grimm

1
Hier ist auch ein guter Link - coderwall.com/p/1zflyg
Arup Rakshit

Antworten:


298

Unterklassen können diese Methoden überschreiben, um unterschiedliche Semantiken bereitzustellen. An Objectsich gibt es zwei wesentliche Unterschiede.

Zuerst clonekopiert die Singleton - Klasse, während dupdies nicht tut.

o = Object.new
def o.foo
  42
end

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

Zweitens clonebleibt der gefrorene Zustand erhalten, während dupdies nicht der Fall ist.

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

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

Die Rubinius-Implementierung für diese Methoden ist oft meine Quelle für Antworten auf diese Fragen, da sie ziemlich klar ist und eine ziemlich konforme Ruby-Implementierung darstellt.


15
Falls jemand versucht, dies erneut zu ändern: Die "Singleton-Klasse", ein in Ruby gut definierter Begriff, enthält nicht nur die Singleton- Methoden , sondern auch alle in der Singleton-Klasse definierten Konstanten. Bedenken Sie : o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman

3
Tolle Antwort, gefolgt von einem tollen Kommentar, aber es führte mich auf eine wilde Gänsejagd, um diese Syntax zu verstehen. Dies wird allen anderen da draußen helfen, die ebenfalls verwirrt sein könnten: devalot.com/articles/2008/09/ruby-singleton
davidpm4

1
Ich denke, es ist erwähnenswert, dass die "Singleton-Klasse" auch alle Module enthält, die extendfür das ursprüngliche Objekt bearbeitet wurden. So Object.new.extend(Enumerable).dup.is_a?(Enumerable)gibt false zurück.
Daniel

Obwohl diese Antworten die Frage beantworten und die Unterschiede angeben. Es ist auch erwähnenswert, dass beide Methoden für unterschiedliche Situationen gedacht sind, wie in der Object # dup- Dokumentation angegeben. Der Anwendungsfall für das Klonen ist das Klonen eines Objekts mit der Absicht, es als dieselbe Instanz zu verwenden (mit einer anderen Objekt-ID) dup ein Objekt als Basis für eine neue Instanz duplizieren soll.
3limin4t0r

189

Beim Umgang mit ActiveRecord gibt es auch einen signifikanten Unterschied:

dup Erstellt ein neues Objekt, ohne dass seine ID festgelegt ist. Sie können also ein neues Objekt in der Datenbank speichern, indem Sie auf klicken .save

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

clone Erstellt ein neues Objekt mit derselben ID, sodass alle an diesem neuen Objekt vorgenommenen Änderungen den ursprünglichen Datensatz überschreiben, wenn sie getroffen werden .save

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

43
DIESE Antwort enthält IMO die wichtigsten praktischen Informationen. Die anderen Antworten beziehen sich auf Esoterik, während diese Antwort einen kritischen praktischen Unterschied aufzeigt.
JPW

37
Das Obige ist jedoch spezifisch für ActiveRecord. Die Unterscheidung ist in Standard-Ruby weitaus subtiler.
ahmacleod

1
@Stefan und @jvalanen: Wenn ich mich für mein Objekt bewerbe dupund cloneMethoden verwende, erhalte ActiveRecordich umgekehrte Ergebnisse von dem, was Sie in der Antwort erwähnt haben. Das heißt, wenn ich es verwende dup, erstellt es ein neues Objekt id, während clonees festgelegt wird, und während der Verwendung wird ein Objekt erstellt, ohne dass es idfestgelegt wird. Kannst du es bitte noch einmal untersuchen und klären? . Thnx
Huzefa Biyawarwala

Auch in Rails 5 hat sich nichts geändert: api.rubyonrails.org/classes/ActiveRecord/… . Also ich glaube, es gibt etwas Besonderes in Ihrem Fall ...
Jvalanen

Ein cloneneuer Datensatz, der noch nie gespeichert wurde, sollte dann aber ziemlich sicher sein? Kann ich auf diese Weise ein "Vorlagenobjekt" erstellen und klonen, um bestimmte Instanzen zu speichern?
Cyril Duchon-Doris

30

Ein Unterschied besteht bei gefrorenen Objekten. Das cloneeines eingefrorenen Objekts wird ebenfalls eingefroren (während das dupeines eingefrorenen Objekts nicht eingefroren ist).

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

Ein weiterer Unterschied besteht bei Singleton-Methoden. Dieselbe Geschichte hier, dupkopiert diese nicht, clonetut es aber .

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!

Das war sehr nützlich für mich. Wenn Sie einen eingefrorenen konstanten Wert erstellen und an Folgendes übergeben : github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (Rails-Cookie-Behandlung), kann leicht ein Fehler auftreten Wenn sie Ihnen nicht bekannt sind, klonen sie es und versuchen dann, den Klon zu ändern. Wenn Sie Ihren eingefrorenen Wert täuschen und diesen übergeben, können Sie zumindest garantieren, dass niemand versehentlich Ihre Konstante ändert, ohne das Rack hier zu beschädigen.
XP84

4

Beide sind fast identisch, aber der Klon macht eine Sache mehr als dup. Beim Klonen wird auch der eingefrorene Zustand des Objekts kopiert. In dup wird es immer aufgetaut.

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

4

Das neuere Dokument enthält ein gutes Beispiel:

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

Sie können Clone verwenden, um prototypbasierte Programmierung in Ruby durchzuführen. Die Ruby-Objektklasse definiert sowohl die Klonmethode als auch die Dup-Methode. Sowohl Klon als auch Dup erzeugen eine flache Kopie des Objekts, das kopiert wird. Das heißt, die Instanzvariablen des Objekts werden kopiert, nicht jedoch die Objekte, auf die sie verweisen. Ich werde ein Beispiel zeigen:

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"

Beachten Sie im obigen Beispiel, dass der orangefarbene Klon den Status (dh die Instanzvariablen) des Apple-Objekts kopiert. Wenn das Apple-Objekt jedoch auf andere Objekte (z. B. die String-Objektfarbe) verweist, werden diese Verweise nicht kopiert. Stattdessen verweisen Apfel und Orange auf dasselbe Objekt! In unserem Beispiel ist die Referenz das Zeichenfolgenobjekt 'rot'. Wenn Orange die Append-Methode << verwendet, um das vorhandene String-Objekt zu ändern, ändert es das String-Objekt in 'red orange'. Dies ändert in der Tat auch apple.color, da beide auf dasselbe String-Objekt zeigen.

Als Randnotiz weist der Zuweisungsoperator = ein neues Objekt zu und zerstört so eine Referenz. Hier ist eine Demonstration:

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'

Wenn wir im obigen Beispiel der Farbinstanzmethode des orangefarbenen Klons ein neues Objekt zugewiesen haben, verweist es nicht mehr auf dasselbe Objekt wie Apple. Daher können wir jetzt die Farbmethode von Orange ändern, ohne die Farbmethode von Apple zu beeinflussen. Wenn wir jedoch ein anderes Objekt von Apple klonen, verweist dieses neue Objekt auf dieselben Objekte in kopierten Instanzvariablen wie Apple.

dup erstellt auch eine flache Kopie des Objekts, das kopiert wird. Wenn Sie dieselbe Demonstration wie oben für dup ausführen, sehen Sie, dass dies genauso funktioniert. Es gibt jedoch zwei Hauptunterschiede zwischen Klon und Dup. Erstens kopiert der Klon, wie andere bereits erwähnt haben, den eingefrorenen Zustand und dup nicht. Was bedeutet das? Der Begriff "eingefroren" in Ruby ist ein esoterischer Begriff für unveränderlich, der selbst eine Nomenklatur in der Informatik ist, was bedeutet, dass etwas nicht geändert werden kann. Daher kann ein eingefrorenes Objekt in Ruby in keiner Weise geändert werden. es ist praktisch unveränderlich. Wenn Sie versuchen, ein eingefrorenes Objekt zu ändern, löst Ruby eine RuntimeError-Ausnahme aus. Da der Klon den eingefrorenen Status kopiert, wird beim Versuch, ein geklontes Objekt zu ändern, eine RuntimeError-Ausnahme ausgelöst. Umgekehrt, da dup den eingefrorenen Zustand nicht kopiert,

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

Zweitens und interessanterweise kopiert der Klon die Singleton-Klasse (und damit ihre Methoden)! Dies ist sehr nützlich, wenn Sie eine prototypbasierte Programmierung in Ruby durchführen möchten. Lassen Sie uns zunächst zeigen, dass die Singleton-Methoden tatsächlich mit dem Klon kopiert werden, und dann können wir sie in einem Beispiel für die prototypbasierte Programmierung in Ruby anwenden.

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 

Wie Sie sehen können, wird die Singleton-Klasse der Fruchtobjektinstanz in den Klon kopiert. Und daher hat das geklonte Objekt Zugriff auf die Singleton-Methode: seeded?. Dies ist jedoch bei dup nicht der Fall:

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

In der prototypbasierten Programmierung gibt es jetzt keine Klassen, die andere Klassen erweitern und dann Instanzen von Klassen erstellen, deren Methoden von einer übergeordneten Klasse abgeleitet sind, die als Blaupause dient. Stattdessen haben Sie ein Basisobjekt und erstellen dann ein neues Objekt aus dem Objekt, dessen Methoden und Status kopiert wurden (da wir flache Kopien per Klon erstellen, werden natürlich alle Objekte, auf die die Instanzvariablen verweisen, wie in JavaScript freigegeben Prototypen). Sie können dann den Status des Objekts ausfüllen oder ändern, indem Sie die Details der geklonten Methoden eingeben. Im folgenden Beispiel haben wir ein Basisobstobjekt. Alle Früchte haben Samen, also erstellen wir eine Methode number_of_seeds. Aber Äpfel haben einen Samen, und so erstellen wir einen Klon und füllen die Details aus. Wenn wir jetzt Apfel klonen, haben wir nicht nur die Methoden geklont, sondern auch den Staat! Denken Sie daran, dass der Klon eine flache Kopie des Status erstellt (Instanzvariablen). Und aus diesem Grund hat red_apple automatisch 1 Samen, wenn wir Apfel klonen, um einen red_apple zu erhalten! Sie können sich red_apple als ein Objekt vorstellen, das von Apple erbt, das wiederum von Fruit erbt. Deshalb habe ich Obst und Apfel groß geschrieben. Wir haben die Unterscheidung zwischen Klassen und Objekten dank des Klons aufgehoben.

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 

Natürlich können wir eine Konstruktormethode in der protoype-basierten Programmierung haben:

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 

Letztendlich können Sie mit Klon etwas Ähnliches wie das Verhalten des JavaScript-Prototyps erhalten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.