2008-09-30 25 views
35

¿Cómo puedo agregar una variable de instancia a una clase definida en runtime, y luego obtener y establecer su valor desde fuera de la clase?Agregando una variable de instancia a una clase en Ruby

Estoy buscando una solución de metaprogramación que me permita modificar la instancia de clase en tiempo de ejecución en lugar de modificar el código fuente que originalmente definió la clase. Algunas de las soluciones explican cómo declarar variables de instancia en las definiciones de clases, pero eso no es lo que estoy preguntando.

+1

Puede que no se haya aclarado, pero literalmente puede volver a abrir la clase en cualquier momento con la clase ClassName; (cosas nuevas aquí); fin. Esto agrega cosas a una clase existente. Limpio, ¿eh? – webmat

+0

Sí, el webmat es correcto ... Todas las respuestas a continuación funcionan en cualquier archivo que desee cuando lo desee ... debido a algo llamado "clases abiertas", lo que significa que puede modificar una clase en cualquier momento que desee a través de la palabra clave class. –

Respuesta

15

Puede utilizar descriptores de acceso de atributos:

class Array 
    attr_accessor :var 
end 

Ahora se puede acceder a él a través de:

array = [] 
array.var = 123 
puts array.var 

Tenga en cuenta que también se puede utilizar attr_reader o attr_writer para definir sólo getters o setters o se puede definir de forma manual como tal:

class Array 
    attr_reader :getter_only_method 
    attr_writer :setter_only_method 

    # Manual definitions equivalent to using attr_reader/writer/accessor 
    def var 
    @var 
    end 

    def var=(value) 
    @var = value 
    end 
end 

También puede utilizar métodos simples si lo que desea es definido en una sola ejemplo:

array = [] 

def array.var 
    @var 
end 

def array.var=(value) 
    @var = value 
end 

array.var = 123 
puts array.var 

su información, en respuesta a la observación sobre esta respuesta, el método de Singleton funciona bien, y la siguiente es la prueba:

irb(main):001:0> class A 
irb(main):002:1> attr_accessor :b 
irb(main):003:1> end 
=> nil 
irb(main):004:0> a = A.new 
=> #<A:0x7fbb4b0efe58> 
irb(main):005:0> a.b = 1 
=> 1 
irb(main):006:0> a.b 
=> 1 
irb(main):007:0> def a.setit=(value) 
irb(main):008:1> @b = value 
irb(main):009:1> end 
=> nil 
irb(main):010:0> a.setit = 2 
=> 2 
irb(main):011:0> a.b 
=> 2 
irb(main):012:0> 

Como se puede ver, el método de Singleton setit fijará el mismo campo, @b, como el que se define utilizando la attr_accessor ... por lo que un método de Singleton es un enfoque perfectamente válida a esta pregunta.

+0

Si va a utilizar métodos singleton, entonces debería actuar sobre variables de clase en lugar de variables de instancia. Esto no le dará el resultado que está buscando. – Sixty4Bit

+1

Salida explicación de _why aquí: http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html – Sixty4Bit

+0

que le dará el resultado que busca ... Sólo comprobado ..... –

62

Ruby proporciona métodos para esto, instance_variable_get y instance_variable_set. (docs)

Puede crear y asignar un nuevo variables de instancia como esta:

>> foo = Object.new 
=> #<Object:0x2aaaaaacc400> 

>> foo.instance_variable_set(:@bar, "baz") 
=> "baz" 

>> foo.inspect 
=> #<Object:0x2aaaaaacc400 @bar=\"baz\"> 
+0

Esto funciona si solo lo desea para una instancia particular de la clase ... pero yo preferiría definir métodos singleton en la instancia en lugar de utilizar esos métodos –

+3

Mike, creo que ese es el objetivo real de esta pregunta. Él está preguntando cómo agregar una variable "en tiempo de ejecución", de ahí el uso de instance_variable_set en lugar de definirlo en el código. Más que probable "baz" también sería otra variable. – Sixty4Bit

+0

método singleton aún logrará lo mismo, consulte mi salida IRB en la parte inferior de mi respuesta. –

2

Mike Stone's answer ya es bastante completo, pero me gustaría añadir un pequeño detalle.

Puede modificar su clase en cualquier momento, incluso después de haber creado una instancia, y obtener los resultados que desee. Puede probarlo en su consola:

s1 = 'string 1' 
s2 = 'string 2' 

class String 
    attr_accessor :my_var 
end 

s1.my_var = 'comment #1' 
s2.my_var = 'comment 2' 

puts s1.my_var, s2.my_var 
0

sólo lectura, en respuesta a tu edición:

Editar: Parece que tengo que aclarar que estoy buscando una solución metaprogramming eso me permite modificar la instancia de clase en tiempo de ejecución en lugar de modificando el código fuente que definió originalmente la clase. Algunas de las soluciones de explican cómo declarar las variables de instancia en las definiciones de clase , pero eso no es lo que solicito . Perdón por la confusion.

Creo que no entiende el concepto de "clases abiertas", lo que significa que puede abrir una clase en cualquier momento. Por ejemplo:

class A 
    def hello 
    print "hello " 
    end 
end 

class A 
    def world 
    puts "world!" 
    end 
end 

a = A.new 
a.hello 
a.world 

Lo anterior es el código Ruby perfectamente válido, y las definiciones de clase 2 se puede transmitir a través de múltiples archivos de Ruby. Puede usar el método "define_method" en el objeto Module para definir un nuevo método en una instancia de clase, pero es equivalente a usar las clases abiertas.

"Clases abiertas" en Ruby significa que puede redefinir CUALQUIER clase en CUALQUIER punto en el tiempo ... lo que significa agregar nuevos métodos, redefinir métodos existentes o lo que realmente desee. Parece que la solución de "clase abierta" es realmente lo que está buscando ...

2

Las otras soluciones también funcionarán perfectamente, pero aquí hay un ejemplo usando el método de definición _, si está empeñado en no usar abrir clases ... definirá la variable "var" para la clase de matriz ... pero tenga en cuenta que es EQUIVALENTE utilizar una clase abierta ... la ventaja es que puede hacerlo para una clase desconocida (por lo que cualquier clase de objeto, en lugar de abrir una clase específica) ... también define _ método funcionará dentro de un método, mientras que no se puede abrir una clase dentro de un método.

array = [] 
array.class.send(:define_method, :var) { @var } 
array.class.send(:define_method, :var=) { |value| @var = value } 

Y aquí es un ejemplo de su uso ... matriz2 en cuenta que, una DIFERENTE matriz también tiene los métodos, así que si esto no es lo que quiere, es probable que desee métodos simples, que he explicado en otra publicación

irb(main):001:0> array = [] 
=> [] 
irb(main):002:0> array.class.send(:define_method, :var) { @var } 
=> #<Proc:[email protected](irb):2> 
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value } 
=> #<Proc:[email protected](irb):3> 
irb(main):004:0> array.var = 123 
=> 123 
irb(main):005:0> array.var 
=> 123 
irb(main):006:0> array2 = [] 
=> [] 
irb(main):007:0> array2.var = 321 
=> 321 
irb(main):008:0> array2.var 
=> 321 
irb(main):009:0> array.var 
=> 123 
15

@Readonly

Si su uso de "MiObjeto clase" es un uso de una clase abierta, a continuación, tenga en cuenta que está redefiniendo el método initialize.

En Ruby, no existe la sobrecarga ... solo anulación, o redefinición ... en otras palabras, solo puede haber 1 instancia de un método determinado, por lo que si la redefines se redefine. .y el método de inicialización no es diferente (aunque es lo que usa el nuevo método de objetos de clase).

Por lo tanto, nunca redefina un método existente sin alias primero ... al menos si desea acceder a la definición original. Y redefinir el método de inicialización de una clase desconocida puede ser bastante arriesgado.

En cualquier caso, creo que tengo una solución mucho más simple para usted, que utiliza la metaclase real para definir métodos simples:

m = MyObject.new 
metaclass = class << m; self; end 
metaclass.send :attr_accessor, :first, :second 
m.first = "first" 
m.second = "second" 
puts m.first, m.second 

Se puede utilizar tanto la metaclase y clases abiertas para obtener aún más complicado y hacer algo como:

class MyObject 
    def metaclass 
    class << self 
     self 
    end 
    end 

    def define_attributes(hash) 
    hash.each_pair { |key, value| 
     metaclass.send :attr_accessor, key 
     send "#{key}=".to_sym, value 
    } 
    end 
end 

m = MyObject.new 
m.define_attributes({ :first => "first", :second => "second" }) 

lo anterior es, básicamente, la exposición de la metaclase a través del método "metaclase", a continuación, utilizarlo en definir _ atributos para definir dinámicamente un montón de atributos con attr _ acce ssor, y luego invocando el setter de atributos luego con el valor asociado en el hash.

con Ruby puede ser creativo y hacer lo mismo de muchas maneras diferentes ;-)


su información, en caso de que no lo sabía, utilizando la metaclase como lo he hecho significa que sólo está actuando en la instancia dada del objeto. Por lo tanto, invocar los atributos de definir _ solo definirá esos atributos para esa instancia particular.

Ejemplo:

m1 = MyObject.new 
m2 = MyObject.new 
m1.define_attributes({:a => 123, :b => 321}) 
m2.define_attributes({:c => "abc", :d => "zxy"}) 
puts m1.a, m1.b, m2.c, m2.d # this will work 
m1.c = 5 # this will fail because c= is not defined on m1! 
m2.a = 5 # this will fail because a= is not defined on m2! 
+0

¿No definirá estos métodos un objeto MyObject y no la clase en sí? Entonces esto definiría un m1.a, m1.b donde a y b son métodos singleton para solo ese objeto. – vrish88

+0

@Mike ¿Por qué recibo el error 'en '

': método privado 'first =' llamado por # (NoMethodError)' en su primer fragmento de código? – flyer

0

me escribió una joya para este hace algún tiempo. Se llama "Flexible" y no está disponible a través de rubygems, pero estuvo disponible a través de github hasta ayer. Lo eliminé porque era inútil para mí.

Usted puede hacer

class Foo 
    include Flexible 
end 
f = Foo.new 
f.bar = 1 

con él sin obtener ningún error. Entonces puede establecer y obtener variables de instancia de un objeto sobre la marcha. Si está interesado ... podría subir el código fuente a Github nuevamente. Se necesita alguna modificación para permitir

f.bar? 
#=> true 

como método para pedir el objeto si una instancia de variable "barra" se define o no, pero todo lo demás está en marcha.

Saludos cordiales, musicmatze

+0

Eso parece muy peligroso. Por lo menos, conviertes errores tipográficos en código ejecutable. No puedo pensar en una situación en la que esto sea necesario, y mucho menos una buena idea. – Huliax

0

Parece que todas las respuestas anteriores asumen que usted sabe cuál es el nombre de la clase que desea modificar es cuando usted está escribiendo su código. Bueno, eso no siempre es verdad (al menos, no para mí). Podría estar iterando sobre un montón de clases en las que quiero otorgar alguna variable (por ejemplo, para mantener algunos metadatos o algo así). En ese caso, algo como esto hará el trabajo,

# example classes that we want to tweak 
class Foo;end 
class Bar;end 
klasses = [Foo, Bar] 

# iterating over a collection of klasses 
klasses.each do |klass| 
    # #class_eval gets it done 
    klass.class_eval do 
    attr_accessor :baz 
    end 
end 

# it works 
f = Foo.new 
f.baz # => nil 
f.baz = 'it works' # => "it works" 
b = Bar.new 
b.baz # => nil 
b.baz = 'it still works' # => "it still works" 
Cuestiones relacionadas