Добавление переменной экземпляра в класс в Ruby
Как добавить переменную экземпляра в определенный класс в runtime, а затем получить и установить его значение вне класса?
Я ищу решение метапрограммирования, которое позволяет мне модифицировать экземпляр класса во время выполнения вместо изменения исходного кода, который первоначально определял класс. Некоторые из решений объясняют, как объявлять переменные экземпляра в определениях классов, но это не то, о чем я прошу.
Ответы
Ответ 1
Вы можете использовать ассемблеры атрибутов:
class Array
attr_accessor :var
end
Теперь вы можете получить к нему доступ через:
array = []
array.var = 123
puts array.var
Обратите внимание, что вы также можете использовать attr_reader
или attr_writer
для определения только геттеров или сеттеров или вы можете определить их вручную как таковые:
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
Вы также можете использовать методы singleton, если хотите, чтобы он был определен только в одном экземпляре:
array = []
def array.var
@var
end
def array.var=(value)
@var = value
end
array.var = 123
puts array.var
FYI, в ответ на комментарий к этому ответу, метод singleton работает отлично, и следующее доказательство:
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>
Как вы можете видеть, метод singleton setit
будет устанавливать одно и то же поле @b
, как определено с помощью attr_accessor... поэтому одноэлементный метод является вполне допустимым подходом к этому вопросу.
Ответ 2
Ruby предоставляет методы для этого, instance_variable_get
и instance_variable_set
. (docs)
Вы можете создавать и назначать новые переменные экземпляра следующим образом:
>> foo = Object.new
=> #<Object:0x2aaaaaacc400>
>> foo.instance_variable_set(:@bar, "baz")
=> "baz"
>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">
Ответ 3
@Readonly
Если вы используете "класс MyObject", это использование открытого класса, то обратите внимание, что вы переопределяете метод initialize.
В Ruby нет такой вещи, как перегрузка... только переопределение или переопределение... другими словами, может быть только один экземпляр любого данного метода, поэтому, если вы его переопределите, он будет переопределен... и метод инициализации ничем не отличается (хотя это и используется новым методом объектов класса).
Таким образом, никогда не переопределяйте существующий метод без наложения его сначала... по крайней мере, если вы хотите получить доступ к исходному определению. И переопределение метода инициализации неизвестного класса может быть довольно рискованным.
Во всяком случае, я думаю, у меня есть гораздо более простое решение для вас, которое использует фактический метакласс для определения одноэлементных методов:
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
Вы можете использовать как метакласс, так и открытые классы, чтобы стать еще более сложными и сделать что-то вроде:
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" })
Вышеупомянутое в основном раскрывает метакласс через метод "метакласса", а затем использует его в define_attributes для динамического определения связки атрибутов с attr_accessor и последующего вызова атрибута setter со связанным значением в хеше.
С Ruby вы можете стать творческим и сделать то же самое разными способами: -)
FYI, если вы не знаете, использование метакласса, как я уже сделал, означает, что вы действуете только на данный экземпляр объекта. Таким образом, вызов define_attributes будет определять только те атрибуты для этого конкретного экземпляра.
Пример:
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!
Ответ 4
Ответ Майка Стоуна уже довольно всеобъемлющий, но я хотел бы добавить немного деталей.
Вы можете изменить свой класс в любой момент, даже после того, как какой-то экземпляр был создан, и получить желаемые результаты. Вы можете попробовать его в консоли:
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
Ответ 5
Другие решения тоже будут работать отлично, но вот пример использования метода define_method, если вы чертовски настроены не на использование открытых классов... он определит переменную "var" для класса массива... но обратите внимание, что EQUIVALENT использовать открытый класс... преимущество в том, что вы можете сделать это для неизвестного класса (так что любой класс объекта, а не открытие определенного класса)... также define_method будет работать внутри метода, тогда как вы не можете открыть класс внутри метода.
array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }
И вот пример его использования... обратите внимание, что массив2, массив DIFFERENT также имеет методы, поэтому, если это не то, что вы хотите, вы, вероятно, хотите использовать методы singleton, которые я объяснил в другой должности.
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
Ответ 6
С уважением, в ответ на ваше редактирование:
Изменить: похоже, мне нужно уточнить что я ищу метапрограммирование решение, которое позволяет мне изменять экземпляр класса во время выполнения вместо изменение исходного кода первоначально был определен класс. Несколько решения объясняют, как объявить переменные экземпляра в классе определения, но это не то, что я есть спрашивать о. Извините за путаницу.
Я думаю, что вы не совсем понимаете концепцию "открытых классов", а это означает, что вы можете открыть класс в любое время. Например:
class A
def hello
print "hello "
end
end
class A
def world
puts "world!"
end
end
a = A.new
a.hello
a.world
Вышеописанный код Ruby вполне корректен, и определения двух классов могут быть распределены по нескольким файлам Ruby. Вы можете использовать метод define_method в объекте Module для определения нового метода в экземпляре класса, но он эквивалентен использованию открытых классов.
"Открытые классы" в Ruby означает, что вы можете переопределить ЛЮБОЙ класс в ЛЮБОЙ момент времени... что означает добавление новых методов, переопределение существующих методов или того, что вы действительно хотите. Похоже, что решение "открытого класса" действительно то, что вы ищете...
Ответ 7
Я написал для этого драгоценный камень некоторое время назад. Он назывался "Гибкий" и недоступен через rubygems, но был доступен через github до вчерашнего дня. Я удалил его, потому что это было бесполезно для меня.
Вы можете сделать
class Foo
include Flexible
end
f = Foo.new
f.bar = 1
с ней без каких-либо ошибок. Таким образом, вы можете установить и получить переменные экземпляра из объекта "на лету".
Если вы заинтересованы... я могу снова загрузить исходный код в github. Он нуждается в некоторой модификации, чтобы включить
f.bar?
#=> true
как метод для запроса объекта, если определена переменная экземпляра "bar" или нет, но что-то еще работает.
С уважением, musicmatze
Ответ 8
Похоже, что все предыдущие ответы предполагают, что вы знаете, что имя класса, которое вы хотите настроить, - это когда вы пишете свой код. Ну, это не всегда так (по крайней мере, не для меня). Я мог бы перебирать кучу классов, которые я хочу наделить некоторой переменной (скажем, для хранения некоторых метаданных или чего-то еще). В этом случае что-то вроде этого выполнит эту работу,
# 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"