privados - programacion con objetos ruby
Agregar una variable de instancia a una clase en Ruby (8)
Las otras soluciones también funcionarán perfectamente, pero aquí hay un ejemplo usando define_method, si estás empeñado en no usar clases abiertas ... definirá la variable "var" para la clase de matriz ... pero ten en cuenta que es EQUIVALENTE a usar una clase abierta ... el beneficio es que puedes hacerlo por una clase desconocida (por lo que cualquier clase de objeto, en lugar de abrir una clase específica) ... también define_method funcionará dentro de un método, mientras que no puedes 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í hay un ejemplo de su uso ... tenga en cuenta que array2, una matriz DIFERENTE también tiene los métodos, así que si esto no es lo que quiere, probablemente quiera métodos singleton que expliqué en otra publicación.
irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { @var }
=> #<Proc:0x00007f289ccb62b0@(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value }
=> #<Proc:0x00007f289cc9fa88@(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
¿Cómo puedo agregar una variable de instancia a una clase definida en tiempo de ejecución , y luego obtener y establecer su valor desde fuera de la clase?
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.
Ruby proporciona métodos para esto, instance_variable_get
y instance_variable_set
. ( documentos )
Puede crear y asignar nuevas variables de instancia como esta:
>> foo = Object.new
=> #<Object:0x2aaaaaacc400>
>> foo.instance_variable_set(:@bar, "baz")
=> "baz"
>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=/"baz/">
Solo lectura, en respuesta a su edición:
Editar: Parece que necesito aclarar que estoy buscando una solución de metaprogramación que me permita modificar la instancia de la 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. Perdón por la confusion.
Creo que no entiendes el concepto de "clases abiertas", lo que significa que puedes 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 un código Ruby perfectamente válido, y las 2 definiciones de clase se pueden extender a través de múltiples archivos 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 puedes redefinir CUALQUIER clase en CUALQUIER punto en el tiempo ... lo que significa agregar nuevos métodos, redefinir métodos existentes o lo que realmente quieras. Parece que la solución de "clase abierta" realmente es lo que estás buscando ...
Escribí una joya para esto 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í.
Tu puedes 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ás interesado ... podría subir el código fuente a github de nuevo. Necesita alguna modificación para habilitar
f.bar?
#=> true
como método para preguntar al objeto si una variable de instancia "barra" está definida o no, pero se está ejecutando cualquier otra cosa.
Saludos cordiales, musicmatze
Parece que todas las respuestas anteriores asumen que usted sabe cuál es el nombre de la clase que desea modificar cuando escribe su código. Bueno, eso no siempre es verdad (al menos, no para mí). Podría estar iterando sobre una pila de clases a 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"
Puede usar accesadores de atributos:
class Array
attr_accessor :var
end
Ahora puedes acceder a través de:
array = []
array.var = 123
puts array.var
Tenga en cuenta que también puede usar attr_reader
o attr_writer
para definir solo getters o setters o puede definirlos manualmente como tales:
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 puedes usar métodos singleton si solo quieres que se defina en una sola instancia:
array = []
def array.var
@var
end
def array.var=(value)
@var = value
end
array.var = 123
puts array.var
FYI, en respuesta al comentario sobre esta respuesta, el método singleton funciona bien, y la siguiente es una 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 puede ver, el método singleton setit
establecerá el mismo campo, @b
, que el definido usando attr_accessor ... por lo que un método singleton es un enfoque perfectamente válido para esta pregunta.
@ Readonly
Si su uso de "clase MyObject" es un uso de una clase abierta, tenga en cuenta que está redefiniendo el método de inicialización.
En Ruby, no existe la sobrecarga ... solo anulación, o redefinición ... en otras palabras, solo puede haber 1 instancia de cualquier método dado, así que si la redefines, se redefine ... y la inicialización método 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 usa la metaclase real para definir métodos singleton:
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
Puede usar tanto la metaclase como las clases abiertas para obtener un truco aún más grande 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 básicamente expone la metaclase a través del método de "metaclass", luego la usa en define_attributes para definir dinámicamente un grupo de atributos con attr_accessor, y luego invoca el atributo setter luego con el valor asociado en el hash.
Con Ruby puedes ser creativo y hacer lo mismo de diferentes maneras ;-)
FYI, en caso de que no lo supieras, usar la metaclase como lo hice significa que solo estás actuando en la instancia dada del objeto. Por lo tanto, invocar define_attributes 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!
La respuesta de Mike Stone ya es bastante completa, pero me gustaría agregar un pequeño detalle.
Puedes modificar tu clase en cualquier momento, incluso después de que se haya creado alguna instancia, y obtener los resultados que deseas. Puedes probarlo en tu 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