2011-02-04 6 views
17

Estoy trabajando en una aplicación de rieles pequeños y tengo un problema con el modelo OOP de ruby. Tengo la siguiente estructura de clase simplificada.Inicializando variables de instancia de clase en 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 

Mi problema ahora es que cuando llamo add_bar en la definición de clase de Baz, @bar aparentemente no es inicializado y me sale un error que el operador + no está disponible para nil. Llamar al add_bar en Foo directamente no produce este problema. ¿Por qué es eso y cómo puedo inicializar @bar correctamente?

Para aclarar lo que quiero, señalaré el comportamiento que esperaría de estas clases.

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

¿Cómo puedo lograr esto?

Respuesta

42

Respuesta corta: variables de instancia no se les heredados por las subclases

Respuesta larga: el problema es que usted escribió @bar = [] en el cuerpo de la clase (fuera de cualquier método). Cuando establece una variable de instancia, se almacena en lo que actualmente es self. Cuando estás en un cuerpo de clase, self es el objeto de clase Foo. Entonces, en su ejemplo, @foo se define en el objeto de clase Foo.

Más tarde, cuando intenta buscar una variable de instancia, Ruby busca en lo que actualmente es self. Cuando llamas a add_bar desde Baz, self es Baz. También self es STILL Baz en el cuerpo de add_bar (aunque ese método está en Foo). Entonces, Ruby busca @bar en Baz y no puede encontrarlo (porque lo definió en Foo).

He aquí un ejemplo que podría hacer esto más claro

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 

Este es sin duda un poco confuso, y un punto de tropiezo común que las personas nuevas a Ruby, por lo que no se sienta mal. Este concepto finalmente hizo clic para mí cuando leí el capítulo 'Metaprogramación' en Programming Ruby (también conocido como "The Pickaxe").

Cómo resolvería su problema: Mire el método class_attribute de Rails. Permite el tipo de cosas que estás tratando de hacer (definir un atributo en una clase padre que puede heredarse (y superponerse) en sus subclases).

+2

+1 muy bien contestado –

+0

Parece que los rieles 2.3. *, Que tengo que usar, no tienen la característica de atributo de clase en ActiveSupport, pero class_inheritable_accessor parece hacer exactamente lo que quiero, entonces lo estoy usando ahora. Gracias por el consejo. – HalloDu

1

Esto parece funcionar bien:

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

Sí, Yo probé que uno también, pero el problema es, que la variable @@ bar es global para la clase y es subclases, que tampoco es lo que quiero. Editaré mi pregunta para aclarar exactamente lo que quiero. – HalloDu

+0

Cambio mi respuesta. Ahora creo que eso resuelve tu problema. –

3

Bueno, ya @bar se define como variable de instancia de clase, entonces se limita a la clase Foo. Marque esta :

class Foo 
    @bar = [] 
end 

class Baz < Foo 
end 

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

De todos modos, por este simple ejemplo, usted puede hacer esto:

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 

leer más sobre este here.

2

lo hago de esta manera:

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 

Luego se debe hacer lo que quiera.

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

Tenga en cuenta que el método set_some_var es opcional, se puede hacer SubClass1.some_var = ... si lo prefiere.

Si quieres algo de valor por defecto, añadir algo así como que bajo class << self

def some_var 
    @some_var || 'default value' 
end 
Cuestiones relacionadas