Você pode usar o clone para fazer a programação baseada em protótipo no Ruby. A classe Object do Ruby define o método clone e o método dup. Tanto o clone quanto o dup produzem uma cópia superficial do objeto que está copiando; isto é, as variáveis de instância do objeto são copiadas, mas não os objetos a que se referem. Vou demonstrar um exemplo:
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 no exemplo acima, o clone laranja copia o estado (ou seja, as variáveis de instância) do objeto apple, mas onde o objeto apple faz referência a outros objetos (como a cor do objeto String), essas referências não são copiadas. Em vez disso, maçã e laranja fazem referência ao mesmo objeto! No nosso exemplo, a referência é o objeto de string 'red'. Quando orange usa o método append, <<, para modificar o objeto String existente, ele muda o objeto de string para 'laranja vermelho'. Isso também altera apple.color, pois ambos apontam para o mesmo objeto String.
Como observação lateral, o operador de atribuição, =, atribuirá um novo objeto e, assim, destruirá uma referência. Aqui está uma demonstração:
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'
No exemplo acima, quando atribuímos um novo objeto novo ao método de instância de cor do clone laranja, ele não faz mais referência ao mesmo objeto que a maçã. Portanto, agora podemos modificar o método de cor laranja sem afetar o método de cor da maçã, mas se clonarmos outro objeto da maçã, esse novo objeto fará referência aos mesmos objetos nas variáveis de instância copiadas da maçã.
O dup também produzirá uma cópia superficial do objeto que está copiando e, se você fizer a mesma demonstração mostrada acima no dup, verá que ele funciona exatamente da mesma maneira. Mas existem duas grandes diferenças entre clone e dup. Primeiro, como outros mencionados, o clone copia o estado congelado e o dup não. O que isto significa? O termo 'congelado' em Ruby é um termo esotérico para imutável, que por si só é uma nomenclatura na ciência da computação, o que significa que algo não pode ser mudado. Portanto, um objeto congelado no Ruby não pode ser modificado de forma alguma; é, de fato, imutável. Se você tentar modificar um objeto congelado, o Ruby gerará uma exceção RuntimeError. Como o clone copia o estado congelado, se você tentar modificar um objeto clonado, ele gerará uma exceção RuntimeError. Por outro lado, como o dup não copia o 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
Segundo, e, mais interessante, o clone copia a classe singleton (e, portanto, seus métodos)! Isso é muito útil se você deseja realizar uma programação baseada em protótipo no Ruby. Primeiro, vamos mostrar que, de fato, os métodos singleton são copiados com o clone e, em seguida, podemos aplicá-lo em um exemplo de programação baseada em protótipo no 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 você pode ver, a classe singleton da instância do objeto fruit é copiada para o clone. E, portanto, o objeto clonado tem acesso ao método singleton: seeded ?. Mas este não é o caso do dup:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Agora, na programação baseada em protótipo, você não possui classes que estendem outras classes e, em seguida, criam instâncias de classes cujos métodos derivam de uma classe pai que serve como um blueprint. Em vez disso, você tem um objeto base e, em seguida, cria um novo objeto com seus métodos e estado copiados (é claro, como estamos fazendo cópias rasas via clone, todos os objetos aos quais as variáveis de instância referenciam serão compartilhados como no JavaScript protótipos). Você pode preencher ou alterar o estado do objeto preenchendo os detalhes dos métodos clonados. No exemplo abaixo, temos um objeto base de frutas. Como todas as frutas têm sementes, criamos um método number_of_seeds. Mas as maçãs têm uma semente e, por isso, criamos um clone e preenchemos os detalhes. Agora, quando clonamos a maçã, não apenas clonamos os métodos, mas clonamos o estado! Lembre-se de que o clone faz uma cópia superficial do estado (variáveis de instância). E por isso, quando clonamos a maçã para obter uma maçã vermelha, a maçã vermelha automaticamente terá 1 semente! Você pode pensar em red_apple como um objeto que herda da Apple, que por sua vez herda de Fruit. Por isso, capitalizei Fruit e Apple. Acabamos com a distinção entre classes e objetos, cortesia do 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
Obviamente, podemos ter um método construtor em programação baseada em protoype:
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
Por fim, usando o clone, você pode obter algo semelhante ao comportamento do protótipo JavaScript.
dup
e o queclone
faz, mas por que você usaria um e não o outro.