privados - programacion con objetos ruby
¿Cómo puedo cambiar el valor de retorno de un constructor de clase en Ruby? (5)
Tengo una clase, Foo. Quiero poder pasar al constructor una instancia Foo, foo y obtener la misma instancia de nuevo.
En otras palabras, quiero que pase esta prueba:
class Foo; end
foo = Foo.new
bar = Foo.new(foo)
assert_equal foo, bar
Alguien sabe cómo puedo hacer eso? Intenté esto:
class Foo
def initialize(arg = nil)
return arg if arg
end
end
foo = Foo.new
bar = Foo.new(foo)
assert_equal foo, bar # => fails
pero no funciona.
¿Ayuda?
EDITAR
Porque varias personas me han preguntado mi razón de ser:
Estoy haciendo un análisis rápido de muchos datos (muchos TB) y voy a tener muchas instancias de muchos objetos. Para algunos de estos objetos, no tiene sentido tener dos instancias diferentes con los mismos datos. Por ejemplo, uno de esos objetos es un objeto "ventana" (como en la ventana temporal) que tiene dos propiedades: hora de inicio y hora de finalización. Quiero poder utilizar el constructor de cualquiera de estas formas y recuperar un objeto de ventana:
window = Window.new(time_a, time_b)
window = Window.new([time_a, time_b])
window = Window.new(seconds_since_epoch_a, seconds_since_epoch_b)
window = Window.new(window_obj)
window = Window.new(end => time_b, start => time_a)
...
Algún otro objeto que necesita una ventana puede ser instanciado de esta manera:
obj = SomeObj.new(data => my_data, window => window_arg)
No necesariamente sé qué hay en window_arg, y realmente no me importa, aceptará cualquier argumento único que pueda ser interpretado por el constructor de Windows. En el caso de tener una instancia de Windows, preferiría simplemente usar esa instancia. Pero el trabajo de interpretación parece una preocupación del constructor de Windows. De todos modos, como mencioné, estoy agitando muchos TB de datos y creando muchas instancias de cosas. Si se pasa un objeto de ventana, quiero que sea reconocido como un objeto de ventana y utilizado.
Por definición , los constructores deben devolver un objeto recién creado de la clase de la que son miembros, por lo tanto, no, no debe anular este comportamiento.
Además, en Ruby, las llamadas new
se initialize
algún lugar dentro de su cuerpo de método, y su valor de retorno se ignora, por lo que de cualquier manera, el valor que devuelve desde la initialize
no se devolverá desde new
.
Dicho esto, creo que en su caso, es posible que desee crear un método de fábrica que devolverá diferentes objetos Foo
función de los argumentos pasados al método de fábrica:
class Foo
def self.factory(arg = nil)
return arg if arg.kind_of? Foo
Foo.new
end
end
foo = Foo.factory
bar = Foo.factory(foo)
assert_equal foo, bar #passes
initialize
es llamado por new
que ignora su valor de retorno. Básicamente, el new
método predeterminado se ve así (excepto que está implementado en C, no en ruby):
class Class
def new(*args, &blk)
o = allocate
o.send(:initialize, *args, &blk)
o
end
end
Por lo tanto, el objeto recién asignado se devuelve de cualquier manera, sin importar lo que haga en initialize
. La única forma de cambiar eso es anular el new
método, por ejemplo, como este:
class Foo
def self.new(arg=nil)
if arg
return arg
else
super
end
end
end
Sin embargo, desaconsejo encarecidamente esto, ya que va en contra de las muchas expectativas que las personas tienen cuando llaman new
:
- La gente espera que
new
devuelvan un nuevo objeto. Quiero decir que incluso se llamanew
. Si desea un método que no siempre crea un objeto nuevo, probablemente debería llamarlo de otra manera. - Por lo menos la gente espera que
Foo.new
devuelva un objetoFoo
. Tu código devolverá cualquier argumento. Es decir,Foo.new(42)
devolvería 42, un Entero, no un objetoFoo
. Entonces, si vas a hacer esto, al menos solo debes devolver el objeto dado, si es un objetoFoo
.
def Foo.new(arg=nil)
arg || super
end
No funciona para:
class Some
def self.new( str )
SomeMore.new( str )
end
end
#
the Some is parent of SomeMore
class SomeMore < Some
def initialize( str )
@str = str
end
end
Para este caso de uso particular, podría ser mejor utilizar uno de estos enfoques.
Class Foo
def self.new(args=nil)
@@obj ||= super(args)
end
end
Class Foo
def self.new(args)
@@obj = super(args)
end
def self.new
@@obj
end
end
Esto le permite tener solo un objeto que se crea, que puede usarse universalmente, pero devuelve un objeto Foo
, haciéndolo más acorde con las expectativas estándar de un new
método, como señaló Jacob.