È 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.
dup
e cosaclone
fa, ma perché dovresti usare l'uno piuttosto che l'altro.