2011-02-04 2 views
17

Я работаю над небольшим приложением рельсов и имею проблему с моделью OOP от Ruby. У меня есть следующая упрощенная структура класса.Инициализация переменных экземпляра класса в 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 на Foo напрямую не дает этой проблемы. Почему это и как я могу правильно инициализировать @bar?

Чтобы уточнить, что я хочу, я укажу на поведение, которое я ожидал бы от этих классов.

Foo.add_bar ["a", "b"] 
Baz.add_bar ["1", "2"] 
Foo.get_bar # => ["a", "b"] 
Baz.get_bar # => ["a", "b", "1", "2"] 

Как я мог достичь этого?

ответ

42

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

Более длинный ответ: проблема заключается в том, что вы написали @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

+1 хорошо ответил –

+0

Кажется, что rails 2.3. *, Который я должен использовать, не имеет функции class_attribute в ActiveSupport, но class_inheritable_accessor, похоже, делает именно то, что я хочу, поэтому я использую это сейчас. Спасибо за совет. – HalloDu

1

Это, кажется, работает хорошо:

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"] 
+0

Да, я стараюсь, что один тоже, но проблема в том, что значение переменной @@ бара глобально для класса, и это подклассы, которые тоже не то, что я хочу. Я отредактирую свой вопрос, чтобы четко указать, что я хочу. – HalloDu

+0

Я меняю свой ответ. Теперь я думаю, что это решает вашу проблему. –

3

Ну, так как @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 

Подробнее об этом here.

2

Я делаю это так:

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 
Смежные вопросы