Добавление переменной экземпляра в класс в Ruby - PullRequest
36 голосов
/ 30 сентября 2008

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

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

Ответы [ 8 ]

63 голосов
/ 30 сентября 2008

Ruby предоставляет методы для этого, instance_variable_get и instance_variable_set. ( документы )

Вы можете создать и назначить новые переменные экземпляра следующим образом:

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

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

>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">
15 голосов
/ 30 сентября 2008

@ Readonly

Если ваше использование "class MyObject" является использованием открытого класса, то обратите внимание, что вы переопределяете метод initialize.

В Ruby нет такой вещи, как перегрузка ... только переопределение или переопределение ... другими словами, может быть только 1 экземпляр любого данного метода, поэтому, если вы переопределите его, он будет переопределен ... и метод initialize ничем не отличается (хотя это то, что использует новый метод объектов Class).

Таким образом, никогда не переопределяйте существующий метод без предварительного наложения его на псевдоним ... по крайней мере, если вы хотите получить доступ к исходному определению. И переопределение метода инициализации неизвестного класса может быть довольно рискованным.

Во всяком случае, я думаю, у меня есть для вас гораздо более простое решение, которое использует настоящий метакласс для определения одноэлементных методов:

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, а затем вызывает установщик атрибутов с соответствующим значением в хэше.

С Ruby вы можете проявить творческий подход и делать одно и то же разными способами; -)


К вашему сведению, если вы не знали, использование метакласса, как я сделал, означает, что вы действуете только с данным экземпляром объекта. Таким образом, вызов 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!
15 голосов
/ 30 сентября 2008

Вы можете использовать атрибуты доступа:

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

Вы также можете использовать одноэлементные методы, если хотите, чтобы они были определены для одного экземпляра:

array = []

def array.var
  @var
end

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

array.var = 123
puts array.var

К вашему сведению, в ответ на комментарий к этому ответу метод синглтона работает нормально, и следующее является доказательством:

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> 

Как видите, метод синглтона setit установит то же поле, @b, что и поле, определенное с помощью attr_accessor ... поэтому метод синглтона является совершенно правильным подходом к этому вопросу.

2 голосов
/ 30 сентября 2008

Другие решения тоже будут отлично работать, но вот пример использования define_method, если вы одержимы неиспользованием открытых классов ... он определит переменную "var" для класса массива ... но учтите, что ЭТО РАВНОМЕРНО использовать открытый класс ... преимущество в том, что вы можете сделать это для неизвестного класса (так что любой класс объекта, а не открывая определенный класс) ... также define_method будет работать внутри метода, тогда как вы не можете открыть класс внутри метода.

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

И вот пример его использования ... обратите внимание, что массив array2, массив DIFFERENT также имеет методы, так что если это не то, что вам нужно, вы, вероятно, хотите использовать одноэлементные методы, которые я объяснил еще один пост.

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
2 голосов
/ 30 сентября 2008

Ответ Майка Стоуна уже достаточно исчерпывающий, но я бы хотел добавить немного деталей.

Вы можете изменить свой класс в любой момент, даже после создания некоторого экземпляра, и получить желаемые результаты. Вы можете попробовать это в своей консоли:

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 голосов
/ 20 октября 2015

Похоже, что все предыдущие ответы предполагают, что вы знаете, как называется класс, который вы хотите настроить, когда вы пишете свой код. Ну, это не всегда так (по крайней мере, не для меня). Возможно, я перебираю кучу классов, которым я хочу присвоить некоторую переменную (скажем, для хранения метаданных или чего-то еще). В этом случае что-то вроде этого сделает работу,

# 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"
0 голосов
/ 12 ноября 2012

Я написал камень для этого некоторое время назад. Он называется «Гибкий» и не доступен через rubygems, но был доступен через github до вчерашнего дня. Я удалил его, потому что это было бесполезно для меня.

Вы можете сделать

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

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

f.bar?
#=> true

как метод для запроса объекта, определена ли переменная экземпляра "bar" или нет, но все остальное работает

С уважением, musicmatze

0 голосов
/ 30 сентября 2008

Только для чтения, в ответ на ваши изменения:

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

Я думаю, что вы не совсем понимаете концепцию "открытых классов", что означает, что вы можете открыть класс в любое время. Например:

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 означают, что вы можете переопределить ЛЮБОЙ класс в ЛЮБОЙ момент времени ... что означает добавление новых методов, переопределение существующих методов или все, что вы действительно хотите. Похоже, что решение "открытого класса" действительно то, что вы ищете ...

...