Subsecciones


dup, clone e initialize_copy

Otra forma de construir objetos es via los métodos dup y clone. Estos métodos crean un objeto que es una copia del del objeto que recibe el mensaje:

>> a = "hello"
=> "hello"
>> b = a.dup
=> "hello"
>> c = a.clone
=> "hello"
>> [a.__id__, b.__id__, c.__id__]
=> [2150357800, 2150329860, 2150304120]

clone hace una copia mas parecida que dup. Por ejemplo, conserva el estatus de freeze del original, cosa que no hace dup:

>> a = "hello"
=> "hello"
>> a.freeze
=> "hello"
>> a << " world"
TypeError: can't modify frozen string
>> b = a.clone
=> "hello"
>> b << " world"
TypeError: can't modify frozen string
>> c = a.dup
=> "hello"
>> c << " world"
=> "hello world"

clone incluso preserva los singleton methods, esto es, preserva los métodos individuales del objeto. Sin embargo, dup no lo hace.

Métodos Singleton

Un singleton method es un método x que se añade a un objeto obj específico. A partir del momento en el que se añade, ese objeto obj responde el mensaje x, cosa que no hacen los otros métodos de su clase. Sigue un ejemplo:

~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ cat -n singletonmethandclone.rb 
     1  obj = "some object"
     2  def obj.printme
     3    puts "***<#{self}>***"
     4  end
     5  
     6  obj.printme
     7  
     8  b = obj.dup
     9  c = obj.clone
    10  
    11  c.printme
    12  b.printme
En la línea 2 añadimos el método printtime únicamente al objeto obj. El objeto clonado c también puede hacer printtime. No así la copia b obtenida por duplicación:
~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ ruby singletonmethandclone.rb 
***<some object>***
***<some object>***
singletonmethandclone.rb:12: undefined method `printme' for "some object":String (NoMethodError)

Copia Superficial

Cuando clone y dup copian las variables de instancia / atributos del objeto original al objeto final sólo copian las referencias a los valores de esas variables no copian los valores reales: sólo hacen una copia superficial. El siguiente ejemplo ilustra este punto:

~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ cat -n dupclone1.rb 
     1  class Point
     2    attr_accessor :coords
     3    def initialize(* coords)
     4      @coords = coords
     5    end
     6  end
     7  
     8  if __FILE__ == $0
     9    puts "dup"
    10    p = Point.new(1, 2, 3)
    11    q = p.dup
    12    puts p.inspect
    13    puts q.inspect
    14  
    15    q.coords[1] = 4
    16    puts p.inspect
    17    puts q.inspect
    18  
    19    puts "clone"
    20    p = Point.new(1, 2, 3)
    21    q = p.clone
    22    puts p.inspect
    23    puts q.inspect
    24  
    25    q.coords[1] = 4
    26    puts p.inspect
    27    puts q.inspect
    28  end
La ejecución nos muestra que la copia es superficial:
~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ ruby dupclone1.rb 
dup
#<Point:0x100169008 @coords=[1, 2, 3]>
#<Point:0x100168fe0 @coords=[1, 2, 3]>
#<Point:0x100169008 @coords=[1, 4, 3]>
#<Point:0x100168fe0 @coords=[1, 4, 3]>
clone
#<Point:0x100168c20 @coords=[1, 2, 3]>
#<Point:0x100168bf8 @coords=[1, 2, 3]>
#<Point:0x100168c20 @coords=[1, 4, 3]>
#<Point:0x100168bf8 @coords=[1, 4, 3]>
Lo que se necesita es una copia profunda.

initialize_copy

Si una clase define un método initialize_copy, los métodos clone y dup lo invocarán en el objeto copia. Al método initialize_copy se le pasa el objeto original como argumento. El valor retornado por initialize_copy es ignorado:

~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ cat -n dupclone2.rb 
     1  class Point
     2    attr_accessor :coords
     3    def initialize(* coords)
     4      @coords = coords
     5    end
     6  
     7    def initialize_copy(orig)
     8      @coords = orig.coords.dup 
     9      # or better
    10      # @coords = @coords.dup 
    11    end
    12  end
    13  
    14  if __FILE__ == $0
    15    puts "dup"
    16    p = Point.new(1, 2, 3)
    17    q = p.dup
    18    puts p.inspect
    19    puts "q = p.dup = "+q.inspect
    20  
    21    q.coords[1] = 4
    22    puts "Changed q[1] to 4 and p has "+p.inspect
    23    puts "Changed q[1] to 4 and q has "+q.inspect
    24  
    25    puts "clone"
    26    p = Point.new(1, 2, 3)
    27    q = p.clone
    28    puts p.inspect
    29    puts "q = p.clone = "+q.inspect
    30  
    31    q.coords[1] = 4
    32    puts "Changed q[1] to 4 and p has "+p.inspect
    33    puts "Changed q[1] to 4 and q has "+q.inspect
    34  end
La ejecución produce la siguiente salida:
~/rubytesting/TheRubyProgrammingLanguage/Chapter7ClassesAndModules$ ruby dupclone2.rb 
dup
#<Point:0x100168900 @coords=[1, 2, 3]>
q = p.dup = #<Point:0x1001688d8 @coords=[1, 2, 3]>
Changed q[1] to 4 and p has #<Point:0x100168900 @coords=[1, 2, 3]>
Changed q[1] to 4 and q has #<Point:0x1001688d8 @coords=[1, 4, 3]>
clone
#<Point:0x1001683d8 @coords=[1, 2, 3]>
q = p.clone = #<Point:0x1001683b0 @coords=[1, 2, 3]>
Changed q[1] to 4 and p has #<Point:0x1001683d8 @coords=[1, 2, 3]>
Changed q[1] to 4 and q has #<Point:0x1001683b0 @coords=[1, 4, 3]>

Casiano Rodriguez León 2015-06-18