Ruby: hereda el código que funciona con variables de clase

La situación: tengo múltiples clases que deberían contener una variable con un hash de configuración; un hash diferente para cada clase pero igual para todas las instancias de una clase.

Al principio, intenté así

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

Pero pronto notó que no funcionaría de esa manera porque @@ config se mantiene en el contexto de A, no B o C, por lo tanto:

 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" 

Pensé en usarlo así:

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

Ahora, está claro para mí por qué sucede eso, pero no estoy seguro de la razón por la que es así.

¿No podría funcionar de la otra manera, manteniendo la variable de clase en el contexto de la subclase?

Lo que también encontré irritante fue el hecho de que el yo siempre es la subclase, incluso cuando se llama ‘en’ la superclase. A partir de esto, primero esperaba que el código de la superclase se “ejecutara en el contexto de” la subclase.

Algo de aclaración sobre esto sería muy apreciado.

Por otro lado, es probable que tenga que aceptar que funciona de esa manera y que tengo que encontrar otra manera de hacerlo.

¿Hay una forma “meta” de hacer esto? (Lo intenté con class_variable_set, etc., pero sin suerte)

¿O tal vez la idea de ese método ‘init’ sea defectuosa en primer lugar y hay algún otro “patrón” para hacer esto?

Podría simplemente hacer @@ config un hash, manteniendo todas las configuraciones y elegir siempre la correcta, pero me parece un poco incómodo … (¿no hay herencia para resolver este tipo de problema?;)

Las @@variables no son variables de clase. Son variables de jerarquía de clase , es decir, se comparten entre toda la jerarquía de clases, incluidas todas las subclases y todas las instancias de todas las subclases. (Se ha sugerido que uno debería pensar en @@variables más como $$variables , porque en realidad tienen más en común con $globals que con @ivars . De esa manera yace menos confusión. Otros han ido más allá y sugieren que deberían simplemente ser eliminado del idioma.)

Ruby no tiene variables de clase en el sentido de que, por ejemplo, Java (donde se llaman campos estáticos) las tiene. No necesita variables de clase, porque las clases también son objetos, por lo que pueden tener variables de instancia como cualquier otro objeto. Todo lo que tienes que hacer es eliminar los extraños @ s. (Y deberá proporcionar un método de acceso para la variable de instancia de la clase).

 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 

Simplifiquemos esto un poco, ya que A.config es claramente solo un attribute_reader:

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

Y, de hecho, A.init es solo un escritor con un nombre gracioso, así que A.config= el nombre a A.config= y A.config= en escritor, lo que a su vez significa que nuestro par de métodos ahora es solo un par de accesorios. (Como hemos cambiado la API, el código de prueba también tiene que cambiar, obviamente).

 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" 

Sin embargo, no puedo dejar de sentir que hay algo más fundamentalmente dudoso sobre el diseño, si es que lo necesitas.