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.
dup
etclone
fait, mais pourquoi vous devriez utiliser un plutôt que l'autre.