Подтвердить что ты не робот

Добавление переменной экземпляра в класс в Ruby

Как добавить переменную экземпляра в определенный класс в runtime, а затем получить и установить его значение вне класса?

Я ищу решение метапрограммирования, которое позволяет мне модифицировать экземпляр класса во время выполнения вместо изменения исходного кода, который первоначально определял класс. Некоторые из решений объясняют, как объявлять переменные экземпляра в определениях классов, но это не то, о чем я прошу.

4b9b3361

Ответ 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"