web-dev-qa-db-fra.com

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

Les docs Ruby pour dup say:

En général, clone et dup peuvent avoir une sémantique différente dans les classes descendantes. Alors que clone est utilisé pour dupliquer un objet, y compris son état interne, dup utilise généralement la classe de l'objet descendant pour créer la nouvelle instance.

Mais quand je fais quelques tests, j'ai trouvé qu'ils étaient 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

Alors, quelles sont les différences entre les deux méthodes?

205
cali-1337500

Les sous-classes peuvent remplacer ces méthodes pour fournir une sémantique différente. Dans Object même, il y a deux différences principales.

Tout d'abord, clone copie la classe singleton, alors que dup ne le fait pas.

o = Object.new
def o.foo
  42
end

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

Deuxièmement, clone préserve l'état figé, alors que dup ne le fait pas.

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 pour des réponses à ces questions, car elle est très claire et une implémentation de Ruby assez conforme.

288
Jeremy Roman

En ce qui concerne ActiveRecord, il existe également une différence significative:

dup crée un nouvel objet sans définir son identifiant. Vous pouvez donc enregistrer un nouvel objet dans la base de données en cliquant sur .save.

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

clone crée un nouvel objet avec le même identifiant, de sorte que toutes les modifications apportées à ce nouvel objet écraseront l'enregistrement d'origine si vous frappez .save

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

Une différence concerne les objets gelés. La clone d'un objet gelé est également gelée (alors qu'une dup d'un objet gelé ne l'est pas).

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 concerne les méthodes singleton. Même histoire ici, dup ne les copie pas, mais clone le fait.

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!
30
Jonathan Fretheim

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

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

Le plus récent doc inclut 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>
4
Xavier Nayrac

Vous pouvez utiliser clone pour effectuer une programmation basée sur un prototype dans Ruby. La classe Object de Ruby's définit à la fois la méthode clone et la méthode dup. Les deux clones et 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 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"

Notez que 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, Apple et Orange font tous deux 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 d’ajout <<, pour modifier l’objet String existant, il modifie l’objet string en «orange rouge». Cela change également Apple.color, car ils pointent tous deux vers le même objet String.

En remarque, l'opérateur d'affectation, =, assignera 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, celui-ci ne fait plus référence au même objet que Apple. Par conséquent, nous pouvons maintenant modifier la méthode de couleur de l'orange sans affecter la méthode de couleur de Apple, mais si nous clonons un autre objet d'Apple, ce nouvel objet référencera les mêmes objets dans les variables d'instance copiées comme Apple.

dup produira également une copie superficielle de l’objet qu’elle copie, et si vous deviez reproduire la démonstration ci-dessus pour duper, vous verrez que cela fonctionne exactement de la même manière. Mais il existe deux différences majeures entre clone et dup. Premièrement, comme d’autres l’ont mentionné, le clone copie l’état gelé et non le dup. Qu'est-ce que ça veut dire? Le terme «gelé» en rubis est un terme ésotérique désignant immuable, qui est lui-même une nomenclature en informatique, ce qui signifie que quelque chose ne peut pas être changé. Ainsi, un objet gelé en Ruby ne peut en aucun cas être modifié; c'est en effet immuable. Si vous essayez de modifier un objet gelé, Ruby lève une exception RuntimeError. Étant donné que clone copie l'état figé, si vous tentez de modifier un objet cloné, une exception RuntimeError sera générée. À l'inverse, dup ne copiant pas l'état figé, aucune exception de ce type ne se produira, comme nous allons le montrer:

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 un prototype en 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 un prototype 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 

Comme vous pouvez le constater, la classe singleton de l'instance de l'objet fruit est copiée dans le clone. Et par conséquent, 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éent 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 effectuons des copies superficielles via un clonage, tous les objets référencés par les variables d'instance seront partagés comme en JavaScript. prototypes). Vous pouvez ensuite renseigner 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, nous avons donc créé un clone et rempli les détails. Maintenant, lorsque nous clonons Apple, nous ne clonons pas seulement les méthodes mais nous clonons l'État! Remember clone effectue une copie superficielle de l'état (variables d'instance). Et à cause de cela, lorsque nous clonons Apple pour obtenir un red_Apple, red_Apple aura automatiquement 1 graine! Vous pouvez considérer red_Apple comme un objet héritant d'Apple, qui hérite à son tour de Fruit. C'est pourquoi j'ai capitalisé Fruits et Pommes. 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 à base de 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.

0
Donato