2011-02-04 13 views
17

Pracuję nad małą aplikacją szyn i mam problem z modelem OOP ruby. Mam uproszczoną strukturę klas.Inicjowanie zmiennych instancji klas w 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 

Mój problem jest teraz, że gdy zgłoszę add_bar w definicji klasy Baz, @bar najwyraźniej nie inicjowany i pojawia się błąd, że + operator nie jest dostępny dla nil. Wywołanie add_bar na Foo bezpośrednio nie daje tego problemu. Dlaczego tak jest i jak mogę poprawnie zainicjować @bar?

Aby wyjaśnić, czego chcę, wskażę zachowanie, jakiego oczekuję od tych klas.

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

Jak mogę to osiągnąć?

Odpowiedz

42

Krótka odpowiedź: instancji zmienne nie uzyskać dziedziczone przez podklasy

Dłuższa odpowiedź: problemem jest to, że napisałeś @bar = [] w ciele klasy (poza jakąkolwiek metodą). Po ustawieniu zmiennej instancji jest ona przechowywana na tym, co aktualnie jest self. Kiedy jesteś w ciele klasy, self jest obiektem klasy Foo. Tak więc w twoim przykładzie, @foo zostaje zdefiniowany na obiekcie klasy Foo.

Później, gdy spróbujesz wyszukać zmienną instancji, Ruby wyszukuje wszystko, co aktualnie jest self. Kiedy wywołujesz add_bar z Baz, self jest Baz. Również self jest STILL Baz w ciele add_bar (mimo że ta metoda jest w Foo). Tak więc Ruby szuka w Bazie @bar i nie może jej znaleźć (ponieważ zdefiniowałeś ją w Foo).

Oto przykład, że może lepiej o tym

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 

To wprawdzie nieco mylące, a wspólny punkt przeszkodą dla ludzi nowych do Ruby, więc nie czuję się źle. Ta koncepcja ostatecznie kliknęła na mnie, gdy przeczytałem rozdział "Metaprogramming" w Programowanie Ruby (znany również jako "The Pickaxe").

Jak mogę rozwiązać twój problem: Spójrz na metodę Rails 'class_attribute. Pozwala na coś, co próbujesz zrobić (definiowanie atrybutu klasy nadrzędnej, która może zostać odziedziczona (i zastąpiona) w swoich podklasach).

+2

+1 ładnie odpowiedział –

+0

Wygląda na to, że tory 2.3. *, Które muszę użyć, nie mają funkcji class_attribute w ActiveSupport, ale class_inheritable_accessor wydaje się robić dokładnie to, co chcę, więc używam tego teraz. Dzięki za wskazówkę. – HalloDu

1

To wydaje się działać dobrze:

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

Tak, próbowałam, że jeden zbyt, ale problemem jest to, że zmienna bar @@ jest globalne dla klasy i jest to podklasy, co także nie jest tym, czego chcę. Zmienię moje pytanie, aby jasno określić, czego chcę. – HalloDu

+0

Zmieniam moją odpowiedź. Teraz myślę, że rozwiązuje twój problem. –

3

cóż, skoro @bar jest definiowana jako zmiennej instancji klasy to jest ograniczone do klasy Foo. Sprawdź to:

class Foo 
    @bar = [] 
end 

class Baz < Foo 
end 

Foo.instance_variables #=> [:@bar] 
Baz.instance_variables #=> [] 

W każdym razie, na tym prostym przykładzie można to zrobić:

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 

Czytaj więcej o tym here.

2

robię to tak:

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 

Następnie należy zrobić, co chcesz.

[8] pry(main)> puts SubClass1.some_var 
foo 
[9] pry(main)> puts SubClass2.some_var 
bar 

Należy zauważyć, że metoda set_some_var jest opcjonalne, można robić SubClass1.some_var = ... jeśli wolisz.

Jeśli chcesz jakąś wartość domyślną, dodać coś takiego pod class << self

def some_var 
    @some_var || 'default value' 
end 
Powiązane problemy