Инициализация переменных экземпляра класса в Ruby
Я работаю над небольшим рельсовым приложением и имею проблему с моделью рубинового ООП. У меня есть следующая упрощенная структура классов.
class Foo
protected
@bar = []
def self.add_bar(val)
@bar += val
end
def self.get_bar
@bar
end
end
class Baz < Foo
add_bar ["a", "b", "c"]
end
Моя проблема в том, что когда я вызываю add_bar в определении класса Baz, @bar
, по-видимому, не инициализирован, и я получаю сообщение об ошибке, что оператор +
недоступен для nil
. Вызов add_bar
on Foo
напрямую не дает этой проблемы. Почему это и как я могу правильно инициализировать @bar
?
Чтобы понять, что я хочу, я укажу на поведение, которое я ожидал бы от этих классов.
Foo.add_bar ["a", "b"]
Baz.add_bar ["1", "2"]
Foo.get_bar # => ["a", "b"]
Baz.get_bar # => ["a", "b", "1", "2"]
Как я мог достичь этого?
Ответы
Ответ 1
Короткий ответ: переменные экземпляра не наследуются подклассами
Более длинный ответ: проблема заключается в том, что вы написали @bar = []
в теле класса (вне любого метода). Когда вы устанавливаете переменную экземпляра, она сохраняется на все, что есть в настоящее время self
. Когда вы находитесь в классе класса, self
- это объект класса Foo. Итак, в вашем примере, @foo
определяется на объекте класса Foo.
Позже, когда вы пытаетесь найти переменную экземпляра, Ruby просматривает все, что в настоящее время self
. Когда вы вызываете add_bar из Baz, self
- Baz. Также self
является STILL Baz в теле add_bar (хотя этот метод находится в Foo). Итак, Ruby ищет @bar
в Baz и не может его найти (потому что вы определили его в Foo).
Вот пример, который может сделать это более понятным
class Foo
@bar = "I'm defined on the class object Foo. self is #{self}"
def self.get_bar
puts "In the class method. self is #{self}"
@bar
end
def get_bar
puts "In the instance method. self is #{self} (can't see @bar!)"
@bar
end
end
>> Foo.get_bar
In the class method. self is Foo
=> "I'm defined on the class object Foo. self is Foo"
>> Foo.new.get_bar
In the instance method. self is #<Foo:0x1056eaea0> (can't see @bar!)
=> nil
Это, по общему признанию, немного запутанно, и общая точка преткновения для людей, новых для Ruby, так что не чувствуйте себя плохо. Эта концепция, наконец, нажала на меня, когда я прочитал главу "Метапрограммирование" в "Программирование Ruby" (также известный как "The Pickaxe" ).
Как бы я решил вашу проблему: Посмотрите на Rails 'class_attribute
. Это позволяет делать то, что вы пытаетесь сделать (определяя атрибут родительского класса, который может наследоваться (и переопределять) в своих подклассах).
Ответ 2
Ну, так как @bar определяется как переменная экземпляра класса, то она ограничивается классом Foo.
Проверьте это:
class Foo
@bar = []
end
class Baz < Foo
end
Foo.instance_variables #=> [:@bar]
Baz.instance_variables #=> []
В любом случае, для этого простого примера вы можете сделать это:
class Foo
protected
def self.add_bar(val)
@bar ||=[]
@bar += val
end
def self.get_bar
@bar
end
end
class Baz < Foo
add_bar ["a", "b", "c"]
end
Подробнее об этом здесь.
Ответ 3
Я делаю это так:
class Base
class << self
attr_accessor :some_var
def set_some_var(value)
self.some_var = value
end
end
end
class SubClass1 < Base
set_some_var :foo
end
class SubClass2 < Base
set_some_var :bar
end
Затем он должен делать то, что вы хотите.
[8] pry(main)> puts SubClass1.some_var
foo
[9] pry(main)> puts SubClass2.some_var
bar
Обратите внимание, что метод set_some_var не является обязательным, вы можете сделать SubClass1.some_var = ...
, если хотите.
Если вы хотите какое-то значение по умолчанию, добавьте что-то вроде этого в class << self
def some_var
@some_var || 'default value'
end
Ответ 4
Это работает хорошо:
class Foo
protected
@@bar = {}
def self.add_bar(val)
@@bar[self] ||= []
@@bar[self] += val
end
def self.get_bar
(self == Foo ? [] : @@bar[Foo] || []) + (@@bar[self] || [])
end
end
class Baz < Foo
end
Foo.add_bar ["a", "b"]
Baz.add_bar ["1", "2"]
Foo.get_bar # => ["a", "b"]
Baz.get_bar # => ["a", "b", "1", "2"]