Puedes usar clone para hacer programación basada en prototipos en Ruby. La clase Object de Ruby define tanto el método de clonación como el método dup. Tanto clone como dup producen una copia superficial del objeto que está copiando; es decir, las variables de instancia del objeto se copian pero no los objetos a los que hacen referencia. Voy a demostrar un ejemplo:
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"
Observe que en el ejemplo anterior, el clon naranja copia el estado (es decir, las variables de instancia) del objeto apple, pero cuando el objeto apple hace referencia a otros objetos (como el color del objeto String), esas referencias no se copian. En cambio, ¡manzana y naranja hacen referencia al mismo objeto! En nuestro ejemplo, la referencia es el objeto de cadena 'rojo'. Cuando orange usa el método append, <<, para modificar el objeto String existente, cambia el objeto string a 'red orange'. En efecto, esto también cambia apple.color, ya que ambos apuntan al mismo objeto String.
Como nota al margen, el operador de asignación, =, asignará un nuevo objeto y, por lo tanto, destruirá una referencia. Aquí hay una demostración:
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'
En el ejemplo anterior, cuando asignamos un nuevo objeto nuevo al método de instancia de color del clon naranja, ya no hace referencia al mismo objeto que la manzana. Por lo tanto, ahora podemos modificar el método de color naranja sin afectar el método de color de apple, pero si clonamos otro objeto de apple, ese nuevo objeto hará referencia a los mismos objetos en las variables de instancia copiadas como apple.
dup también producirá una copia superficial del objeto que está copiando, y si hiciera la misma demostración que se muestra arriba a dup, verá que funciona exactamente de la misma manera. Pero hay dos diferencias principales entre clonar y dup. Primero, como otros mencionaron, clone copia el estado congelado y dup no. ¿Qué significa esto? El término 'congelado' en Ruby es un término esotérico para inmutable, que en sí mismo es una nomenclatura en informática, lo que significa que algo no puede cambiarse. Por lo tanto, un objeto congelado en Ruby no puede modificarse de ninguna manera; es, en efecto, inmutable. Si intenta modificar un objeto congelado, Ruby generará una excepción RuntimeError. Dado que clonar copia el estado congelado, si intenta modificar un objeto clonado, generará una excepción RuntimeError. Por el contrario, dado que dup no copia el estado congelado,
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
En segundo lugar, y, más interesante, ¡el clon copia la clase singleton (y por lo tanto sus métodos)! Esto es muy útil si desea realizar una programación basada en prototipos en Ruby. Primero, demostremos que, de hecho, los métodos singleton se copian con clon, y luego podemos aplicarlo en un ejemplo de programación basada en prototipos 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
Como puede ver, la clase singleton de la instancia del objeto de fruta se copia en el clon. Y, por lo tanto, el objeto clonado tiene acceso al método singleton: ¿sembrado ?. Pero este no es el caso con dup:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Ahora, en la programación basada en prototipos, no tiene clases que extiendan otras clases y luego cree instancias de clases cuyos métodos se derivan de una clase principal que sirve como modelo. En cambio, tiene un objeto base y luego crea un nuevo objeto a partir del objeto con sus métodos y estado copiados (por supuesto, dado que estamos haciendo copias superficiales a través de clon, cualquier objeto al que se haga referencia de variables de instancia se compartirá tal como en JavaScript prototipos). Luego puede completar o cambiar el estado del objeto completando los detalles de los métodos clonados. En el siguiente ejemplo, tenemos un objeto de fruta base. Todas las frutas tienen semillas, por lo que creamos un método number_of_seeds. Pero las manzanas tienen una semilla, por lo que creamos un clon y completamos los detalles. Ahora cuando clonamos manzana, ¡no solo clonamos los métodos sino que clonamos el estado! Recuerde que el clon hace una copia superficial del estado (variables de instancia). Y por eso, cuando clonemos una manzana para obtener una manzana roja, ¡la manzana roja automáticamente tendrá 1 semilla! Puedes pensar en red_apple como un objeto que hereda de Apple, que a su vez hereda de Fruit. Por eso, capitalicé Fruit and Apple. Eliminamos la distinción entre clases y objetos por cortesía del clon.
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
Por supuesto, podemos tener un método constructor en la programación basada en prototipos:
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 última instancia, utilizando clone, puede obtener algo similar al comportamiento del prototipo de JavaScript.
dup
y quéclone
hace, sino por qué usaría uno en lugar del otro.