Ruby: eredita il codice che funziona con le variabili di class

La situazione: ho più classi che dovrebbero contenere ciascuna una variabile con un hash di configurazione; un hash diverso per ogni class ma uguale per tutte le istanze di una class.

All’inizio, ho provato così

class A def self.init config @@config = config end def config @@config end end class B < A; end class C < A; end 

Ma presto notò che non avrebbe funzionato in quel modo perché @@ config è detenuto nel contesto di A, non B o C, quindi:

 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" 

Ho pensato di usarlo in questo modo:

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

Ora, mi è chiaro perché succede, ma non sono sicuro del motivo per cui sia così.

Non potrebbe funzionare anche nell’altro modo, mantenendo la variabile di class nel contesto della sottoclass?

Ciò che ho trovato anche irritante è il fatto che il sé è sempre la sottoclass anche quando viene chiamata “nella” superclass. Da questo, mi sono inizialmente aspettato che il codice della superclass sia “eseguito nel contesto di” la sottoclass.

Qualche chiarimento su questo sarebbe molto apprezzato.

D’altra parte, probabilmente dovrò accettare che funzioni in questo modo e che devo trovare un altro modo per farlo.

Esiste un modo “meta” per farlo? (Ho provato con class_variable_set ecc. Ma senza fortuna)

O forse l’idea del metodo “init” è viziata in primo luogo e c’è qualche altro “schema” per farlo?

Potrei semplicemente fare @@ config un hash, tenere tutti i configs e scegliere sempre quello giusto, ma lo trovo un po ‘imbarazzante .. (non c’è ereditarietà per risolvere questo tipo di problema?;)

Le @@variables non sono variabili di class. Sono variabili di gerarchia di class , ovvero sono condivise tra l’intera gerarchia di classi, comprese tutte le sottoclassi e tutte le istanze di tutte le sottoclassi. (È stato suggerito che si pensi alle @@variables più come le $$variables , perché in realtà hanno più in comune con $globals che con @ivars . In questo modo c’è meno confusione, altre sono andate oltre e suggeriscono che dovrebbero semplicemente essere rimosso dalla lingua.)

Ruby non ha variabili di class nel senso che, per esempio, Java (dove sono chiamate campi statici) le ha. Non ha bisogno di variabili di class, perché le classi sono anche oggetti e quindi possono avere variabili d’ istanza come qualsiasi altro object. Tutto quello che devi fare è rimuovere gli estranei @ s. (E dovrai fornire un metodo di accesso per la variabile di istanza di class.)

 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 

Semplificiamo un po ‘, dal momento che A.config è chiaramente solo un attributo_reader:

 class A class << self def init config @config = config end attr_reader :config end def config self.class.config end end 

E, in effetti, A.init è solo uno scrittore con un nome divertente, quindi rinominiamolo in A.config= e A.config= uno scrittore, il che a sua volta significa che la nostra coppia di metodi è ora solo una coppia di accessori. (Dal momento che abbiamo cambiato l'API, anche il codice di test deve cambiare, ovviamente).

 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" 

Tuttavia, non posso scuotere la sensazione che ci sia qualcosa di più fondamentalmente incerto sul design, se ne hai bisogno.