Quelle est la différence entre les méthodes Dup et Clone de Ruby?

214

Les documents Ruby pourdup dire:

En général, cloneet duppeut avoir une sémantique différente dans les classes descendantes. While cloneest utilisé pour dupliquer un objet, y compris son état interne, duputilise généralement la classe de l'objet descendant pour créer la nouvelle instance.

Mais quand je fais un test, je trouve qu'ils sont en fait les mêmes:

class Test
   attr_accessor :x
end

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

Quelles sont donc les différences entre les deux méthodes?

cali-1337500
la source
30
Je voudrais savoir non seulement la différence de ce qui dup et clonefait, mais pourquoi vous devriez utiliser un plutôt que l'autre.
Andrew Grimm
1
voici aussi un bon lien - coderwall.com/p/1zflyg
Arup Rakshit

Réponses:

298

Les sous-classes peuvent remplacer ces méthodes pour fournir une sémantique différente. En Objectsoi, il existe deux différences clés.

Tout d'abord, clonecopie la classe singleton, mais duppas.

o = Object.new
def o.foo
  42
end

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

Deuxièmement, clonepréserve l'état gelé, tandis dupque non.

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

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

L' implémentation Rubinius pour ces méthodes est souvent ma source de réponses à ces questions, car elle est assez claire et une implémentation Ruby assez conforme.

Jeremy Roman
la source
15
Au cas où quelqu'un essaierait de changer cela à nouveau: la "classe singleton", qui est un terme bien défini dans Ruby, inclut non seulement les méthodes singleton , mais aussi toutes les constantes définies sur la classe singleton. Considérez: o = Object.new; class << o; A=5; end; puts ( class << o.clone; A; end ); puts ( class << o.dup; A; end ).
Jeremy Roman
3
excellente réponse, suivie d'un excellent commentaire, mais cela m'a conduit à une chasse aux oies sauvages pour comprendre cette syntaxe. cela aidera toute autre personne qui pourrait également être confuse: devalot.com/articles/2008/09/ruby-singleton
davidpm4
1
Je pense qu'il vaut la peine de mentionner que la "classe singleton" comprend également tous les modules qui ont été extendédités sur l'objet d'origine. Object.new.extend(Enumerable).dup.is_a?(Enumerable)Retourne donc faux.
Daniel
Bien que ces réponses répondent à la question et indiquent les différences. Il convient également de noter que les deux méthodes sont destinées à des situations différentes, comme indiqué dans la documentation Object # dup . Le cas d'utilisation de clone consiste à cloner un objet avec l'intention de l'utiliser comme cette même instance (tout en ayant un identifiant d'objet différent), tandis que dup est destiné à dupliquer un objet comme base pour une nouvelle instance.
3limin4t0r
189

Lorsque vous traitez avec ActiveRecord, il y a aussi une différence significative:

dup crée un nouvel objet sans que son id soit défini, vous pouvez donc enregistrer un nouvel objet dans la base de données en appuyant sur .save

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

clone crée un nouvel objet avec le même identifiant, donc toutes les modifications apportées à ce nouvel objet écraseront l'enregistrement d'origine en cas de frappe .save

category2 = category.clone
#=> #<Category id: 1, name: "Favorites">
jvalanen
la source
43
CETTE réponse est celle qui a l'OMI les informations pratiques les plus importantes ... les autres réponses s'attardent sur l'ésotérique, alors que cette réponse met en évidence une différence pratique critique.
jpw
37
Cependant, ce qui précède est spécifique à ActiveRecord; la distinction est beaucoup plus subtile en Ruby standard.
ahmacleod
1
@Stefan et @jvalanen: Quand je pose ma candidature dupet cloneméthodes sur mon ActiveRecordobjet, je reçois des résultats inverses de ce que vous avez mentionné dans la réponse. ce qui signifie que lorsque j'utilise dup, il crée un nouvel objet avec son idréglage et pendant son utilisation clonecrée un objet sans qu'il idsoit défini. pouvez-vous s'il vous plaît examiner à nouveau et clearifier? . Thnx
huzefa biyawarwala
Rien n'a changé non plus dans Rails 5: api.rubyonrails.org/classes/ActiveRecord/… . Donc je crois qu'il y a quelque chose de spécial dans votre cas ...
jvalanen
Cependant, cloneing un nouvel enregistrement qui n'a jamais été enregistré devrait être assez sûr alors? Puis-je créer un "objet modèle" de cette façon et le cloner pour enregistrer des instances spécifiques?
Cyril Duchon-Doris
30

Une différence est avec les objets gelés. Le cloned'un objet gelé est également gelé (contrairement à celui dupd'un objet gelé).

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

Une autre différence réside dans les méthodes singleton. Même histoire ici, dupne les copie pas, mais le clonefait.

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!
Jonathan Fretheim
la source
Cela m'a été très utile. Si vous créez une valeur constante figée et la transmettez à quelque chose comme ceci: github.com/rack/rack/blob/master/lib/rack/utils.rb#L248 (gestion des cookies Rails), vous pouvez facilement obtenir une erreur quand ils ne vous connaissent pas, ils le clonent puis tentent de modifier le clone. duper votre valeur gelée et la transmettre vous permet au moins de garantir que personne ne modifie accidentellement votre constante, sans casser le rack ici.
XP84
4

Les deux sont presque identiques, mais le clone fait plus que dup. En clone, l'état figé de l'objet est également copié. En dup, il sera toujours décongelé.

 f = 'Frozen'.freeze
  => "Frozen"
 f.frozen?
  => true 
 f.clone.frozen?
  => true
 f.dup.frozen?
  => false 
veeresh yh
la source
4

Le nouveau document comprend un bon exemple:

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>
Xavier Nayrac
la source
0

Vous pouvez utiliser clone pour effectuer une programmation basée sur des prototypes dans Ruby. La classe Object de Ruby définit à la fois la méthode clone et la méthode dup. Le clone et le dup produisent une copie superficielle de l'objet qu'il copie; c'est-à-dire que les variables d'instance de l'objet sont copiées mais pas les objets qu'elles référencent. Je vais vous montrer un exemple:

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"

Remarquez dans l'exemple ci-dessus, le clone orange copie l'état (c'est-à-dire les variables d'instance) de l'objet apple, mais lorsque l'objet apple référence d'autres objets (tels que la couleur de l'objet String), ces références ne sont pas copiées. Au lieu de cela, pomme et orange font référence au même objet! Dans notre exemple, la référence est l'objet chaîne "rouge". Lorsque orange utilise la méthode append <<, pour modifier l'objet String existant, il change l'objet chaîne en «orange rouge». Cela modifie également apple.color, car ils pointent tous les deux vers le même objet String.

En remarque, l'opérateur d'affectation, =, affectera un nouvel objet et détruira ainsi une référence. Voici une démonstration:

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'

Dans l'exemple ci-dessus, lorsque nous avons affecté un nouvel objet à la méthode d'instance de couleur du clone orange, il ne fait plus référence au même objet que apple. Par conséquent, nous pouvons maintenant modifier la méthode de couleur d'orange sans affecter la méthode de couleur de pomme, mais si nous clonons un autre objet de pomme, ce nouvel objet référencera les mêmes objets dans les variables d'instance copiées que pomme.

dup produira également une copie superficielle de l'objet qu'il copie, et si vous faisiez la même démonstration ci-dessus pour dup, vous verrez que cela fonctionne exactement de la même manière. Mais il existe deux différences majeures entre clone et dup. Tout d'abord, comme d'autres l'ont mentionné, le clone copie l'état gelé et pas le dup. Qu'est-ce que ça veut dire? Le terme «gelé» en Ruby est un terme ésotérique pour immuable, qui est lui-même une nomenclature en informatique, ce qui signifie que quelque chose ne peut pas être changé. Ainsi, un objet figé dans Ruby ne peut être modifié en aucune façon; il est, en effet, immuable. Si vous essayez de modifier un objet figé, Ruby lèvera une exception RuntimeError. Étant donné que clone copie l'état figé, si vous essayez de modifier un objet cloné, il déclenchera une exception RuntimeError. Inversement, puisque dup ne copie pas l'état figé,

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

Deuxièmement, et, plus intéressant encore, clone copie la classe singleton (et donc ses méthodes)! Ceci est très utile si vous souhaitez entreprendre une programmation basée sur des prototypes dans Ruby. Tout d'abord, montrons qu'en effet les méthodes singleton sont copiées avec clone, puis nous pouvons l'appliquer dans un exemple de programmation basée sur des prototypes dans 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 

Comme vous pouvez le voir, la classe singleton de l'instance d'objet fruit est copiée dans le clone. Et donc l'objet cloné a accès à la méthode singleton: seeded ?. Mais ce n'est pas le cas avec dup:

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

Désormais, dans la programmation basée sur des prototypes, vous n'avez pas de classes qui étendent d'autres classes, puis créez des instances de classes dont les méthodes dérivent d'une classe parente qui sert de modèle. Au lieu de cela, vous avez un objet de base, puis vous créez un nouvel objet à partir de l'objet avec ses méthodes et son état copiés (bien sûr, puisque nous faisons des copies superficielles via un clone, tous les objets auxquels les variables d'instance font référence seront partagés comme dans JavaScript prototypes). Vous pouvez ensuite remplir ou modifier l'état de l'objet en renseignant les détails des méthodes clonées. Dans l'exemple ci-dessous, nous avons un objet fruit de base. Tous les fruits ont des graines, nous créons donc une méthode number_of_seeds. Mais les pommes ont une graine, alors nous créons un clone et remplissons les détails. Maintenant, quand nous clonons une pomme, nous avons non seulement cloné les méthodes, mais nous avons cloné l'État! N'oubliez pas que le clone effectue une copie superficielle de l'état (variables d'instance). Et à cause de cela, lorsque nous clonons une pomme pour obtenir un red_apple, red_apple aura automatiquement 1 graine! Vous pouvez penser à red_apple comme un objet qui hérite d'Apple, qui à son tour hérite de Fruit. C'est pourquoi j'ai capitalisé Fruit et Apple. Nous avons supprimé la distinction entre les classes et les objets grâce à 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 

Bien sûr, nous pouvons avoir une méthode constructeur en programmation basée sur des prototypes:

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 fin de compte, en utilisant clone, vous pouvez obtenir quelque chose de similaire au comportement du prototype JavaScript.

Donato
la source