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

Инициализация переменных экземпляра класса в 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"]

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

4b9b3361

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