programacion privados orientada objetos lenguaje herencia ejemplos con clases clase atributos ruby metaprogramming

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