2009-08-09 17 views
34

Sytuacja: Mam wiele klas, które powinny posiadać zmienną z hashem konfiguracyjnym; inny skrót dla każdej klasy, ale taki sam dla wszystkich instancji klasy.Ruby: Dziedzicz kod działający ze zmiennymi klasy

Początkowo starałem się ten

class A 
    def self.init config 
    @@config = config 
    end 

    def config 
    @@config 
    end 
end 

class B < A; end 
class C < A; end 

Wkrótce jednak zauważyć, że to nie działa w ten sposób, ponieważ @@ config odbywa się w kontekście A, B lub C nie, a więc:

B.init "bar" 
p B.new.config # => "bar" 
p C.new.config # => "bar" - which would be nil if B had it's own @@config 

C.init "foo" 
p B.new.config # => "foo" - which would still be "bar" if C had it's own @@config 
p C.new.config # => "foo" 

Myślałem o użyciu go tak:

modules = [B, C] 
modules.each do |m| 
    m.init(@config[m.name]) 
end 
# ... 
B.new # which should then have the correct config 

teraz jest dla mnie jasne, dlaczego tak się dzieje, ale nie jestem pewien o TH Powód, dla którego tak jest.

Czy to nie działa również w drugą stronę, trzymając zmienną klasy w kontekście podklasy?

To, co uważałem za irytujące, to fakt, że self jest zawsze podklasą, nawet gdy jest nazywana "w" superklasą. Z tego, po pierwsze, spodziewałem się, że kod z superklasy jest "wykonany w kontekście" podklasy.

Niektóre oświecenia na ten temat byłyby bardzo cenne.

Z drugiej strony, prawdopodobnie muszę zaakceptować to działa w ten sposób i że muszę znaleźć inny sposób, aby to zrobić.

Czy istnieje sposób "meta" to zrobić? (Próbowałem z class_variable_set itp. Ale bez powodzenia)

A może to jest cała idea tej metody "init" wadliwej na pierwszym miejscu i jest jakiś inny "wzór", aby to zrobić?

Mogę tylko zrobić @@ config hash, trzymając wszystkie konfiguracje i zawsze wybrać właściwą, ale uważam, że trochę niezręczne .. (nie jest tam dziedziczenie, aby rozwiązać ten rodzaj problemu?;)

Odpowiedz

94

@@variables nie są zmiennymi klasy. Są to zmienne hierarchii klasy, tj. Są one dzielone między całą hierarchię klas, w tym wszystkie podklasy i wszystkie wystąpienia wszystkich podklas. (Zasugerowano, że powinno się myśleć o @@variables bardziej jak o $$variables, ponieważ mają one więcej wspólnego z $globals niż z @ivars. W ten sposób mniej zamieszania, inne poszły dalej i sugerują, że powinny być po prostu usunięte z języka.)

Ruby nie ma zmiennych klasowych w tym znaczeniu, że np. Java (gdzie nazywane są one polami statycznymi) ma je. Nie wymaga ona zmiennych klas, ponieważ klasy są również obiektami, dlatego mogą mieć zmienne instancji zmiennych jak każdy inny obiekt. Jedyne co musisz zrobić, to usunąć obcego @ s. (I będziesz musiał podać metodę akcesora dla zmiennej instancji klasy.)

class A 
    def self.init config 
    @config = config 
    end 

    def self.config # This is needed for access from outside 
    @config 
    end 

    def config 
    self.class.config # this calls the above accessor on self's class 
    end 
end 

Załóżmy uprościć ten kawałek, ponieważ A.config jest wyraźnie tylko attribute_reader:

class A 
    class << self 
    def init config 
     @config = config 
    end 

    attr_reader :config 
    end 

    def config 
    self.class.config 
    end 
end 

I rzeczywiście, A.init tylko pisarz ze śmieszną nazwę, więc niech to zmienić jego nazwę na A.config= i sprawiają, że jest pisarzem, co z kolei oznacza, że ​​nasza para metod jest teraz tylko parą akcesoriów. (Ponieważ zmieniliśmy API, kod testowy musi również zmienić, oczywiście.)

class A 
    class << self 
    attr_accessor :config 
    end 

    def config 
    self.class.config 
    end 
end 

class B < A; end 
class C < A; end 

B.config = "bar" 
p B.new.config # => "bar" 
p C.new.config # => nil 

C.config = "foo" 
p B.new.config # => "bar" 
p C.new.config # => "foo" 

jednak nie mogę oprzeć się wrażeniu, że jest coś bardziej fundamentalnie niepewni o projekcie, jeśli to potrzebne w ogóle.

+0

Nie widzę, jak wzór jest niepewny. Wydaje się, że jest to dość rozsądna rzecz do zrobienia w ogóle. – Chuck

+0

Dokładnie to musiałem wiedzieć, dziękuję bardzo! :) Naprawdę nie wiem, co jeszcze powiedzieć, teraz wszystko jest tak jasne. Metoda init była przeznaczona do ustawiania wielu zmiennych, mam na przykład konfigurację dla uproszczenia. Ale teraz wspomnij o tym, prawdopodobnie nadal jest czystszy z akcesoriami;) Jeszcze raz bardzo dziękuję! –

+0

@Chuck: Na przykład istnieje metoda instancji ('A # config'), która nie wywołuje metody instancji, nie uzyskuje dostępu do stanu instancji ani nie zostaje nadpisana. To może być artefakt z przyciętego przykładu, może to być uzasadniony projekt, ale może nie jest. Również B i C dziedziczą z A, ale niczego nie przesłonią, jednak oczekuje się, że będą mieć różne zachowania zarówno od siebie nawzajem, jak i od A, mimo że wszystkie są identyczne. Znowu: może rozsądny, może nie. Wszystko zależy od kontekstu, który oczywiście jest o wiele za mały w tym przykładzie, aby dojść do rozsądnych wniosków. –

Powiązane problemy