2009-08-09 8 views
34

La situación: Tengo varias clases que debe contener cada una variable con un hash de configuración; un hash diferente para cada clase pero igual para todas las instancias de una clase.Ruby: Heredar código que funciona con variables de clase

Al principio, traté como esto

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

    def config 
    @@config 
    end 
end 

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

Pero pronto se dio cuenta de que no iba a funcionar de esa manera porque @@ configuración se lleva a cabo 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 como esto:

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

Ahora, es claro para mí por qué sucede esto, pero no estoy seguro acerca º La razón por la cual es así.

¿No podría funcionar a la inversa, 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.

Algunos iluminación sobre este sería muy apreciada.

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

¿Hay una manera "meta" para hacer esto? (Lo intenté con class_variable_set etc. pero sin suerte)

¿O tal vez la idea de ese método 'init' es 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?;)

Respuesta

94

El @@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 simplemente deberían eliminarse del lenguaje.)

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 instancia variables al igual que cualquier otro objeto. Todo lo que tienes que hacer es eliminar los @ s extraños. (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 

Vamos a simplificar esto un poco, ya que A.config es claramente sólo 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 sólo un escritor con un nombre divertido, así que vamos a cambiar el nombre a A.config= y conviértalo en escritor, lo que a su vez significa que nuestro par de métodos ahora es solo un par de accesorios. (Desde que cambiamos la API, el código de prueba tiene que cambiar también, 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 evitar la sensación de que hay algo más fundamental dudoso sobre el diseño, si necesita este en absoluto.

+0

No veo cómo el diseño es dudoso. Parece una cosa bastante razonable de hacer en general. – Chuck

+0

Eso es exactamente lo que necesitaba saber, ¡muchas gracias! :) Realmente no sé qué más decir, todo está muy claro ahora. El método init estaba destinado a configurar múltiples variables, obtuve el ejemplo de configuración para simplificar. Pero ahora que lo mencionas, es probable que esté aún más limpio con los accesorios;) Nuevamente, ¡muchas gracias! –

+0

@Chuck: por ejemplo, hay un método de instancia ('A # config') que no llama a un método de instancia ni accede al estado de instancia ni se anula. Eso podría ser un artefacto del ejemplo recortado, podría ser un diseño legítimo, pero, tal vez, no lo es. Además, B y C heredan de A, pero no anulan nada, sin embargo, de alguna manera se espera que tengan un comportamiento diferente tanto el uno como el otro, aunque todos sean idénticos. De nuevo: tal vez sensato, tal vez no. Todo depende del contexto, que por supuesto es demasiado pequeño en este ejemplo para llegar a una conclusión sensata. –

Cuestiones relacionadas