2010-05-02 8 views
17

Tengo una clase de Ruby llamada LibraryItem. Quiero asociar con cada instancia de esta clase una matriz de atributos. Esta matriz es largo y se ve algo comoVariables de instancia de la clase Ruby y herencia

['title', 'authors', 'location', ...] 

Tenga en cuenta que estos atributos no se supone realmente ser métodos, sólo una lista de atributos que un LibraryItem tiene.

A continuación, quiero hacer una subclase de LibraryItem llama LibraryBook que tiene una serie de atributos que incluye todos los atributos de LibraryItem pero también incluirá muchos más.

Eventualmente querrá varias subclases de LibraryItem cada uno con su propia versión de la matriz @attributes pero cada uno la adición a LibraryItem 's @attributes (por ejemplo, LibraryBook, LibraryDVD, LibraryMap, etc.).

Por lo tanto, aquí está mi intento:

class LibraryItem < Object 
    class << self; attr_accessor :attributes; end 
    @attributes = ['title', 'authors', 'location',] 
end 

class LibraryBook < LibraryItem 
    @attributes.push('ISBN', 'pages') 
end 

Esto no funciona. Me sale el error

undefined method `push' for nil:NilClass 

Si fuera a trabajar, me gustaría que algo como esto

puts LibraryItem.attributes 
puts LibraryBook.attributes 

a la salida

['title', 'authors', 'location'] 
['title', 'authors', 'location', 'ISBN', 'pages'] 

(Añadido: 02-May-2010) Una de las soluciones para esto es para hacer @attributes una variable de instancia simple y luego agregar los nuevos atributos para LibraryBoot en el método initialize (esto fue sugerido por demas en uno o f las respuestas).

Si bien esto ciertamente funcionaría (y es, de hecho, lo que he estado haciendo todo el tiempo), no estoy contento con esto ya que es subóptimo: ¿por qué deberían construirse estas matrices invariables cada vez que un objeto es ¿creado?

Lo que realmente quiero es tener variables de clase que puedan heredar de una clase principal, pero cuando cambien en la clase secundaria no cambien en la clase principal.

Respuesta

7

Ya que mencionan que los atributos son "fijas" e "inmutables", supongo que quiere decir que nunca cambiará su valor una vez que se crea el objeto. En ese caso, algo como lo siguiente debería funcionar:

class Foo 
    ATTRS = ['title', 'authors', 'location'] 
    def attributes 
     ATTRS 
    end 
end 

class Bar < Foo 
    ATTRS = ['ISBN', 'pages'] 
    def attributes 
     super + ATTRS 
    end 
end 

Está implementando un método manual lector (en lugar de dejar attr_accessor crearlo para usted) que oculta el nombre interno de la matriz. En su subclase, simplemente llame a la función de lectura de la clase antecesora, vire los campos adicionales asociados con la clase secundaria y devuélvalos a la persona que llama. Para el usuario, esto parece una variable de miembro de solo lectura llamada attributes que tiene valores adicionales en la subclase.

-1

Puede hacerlo usando CINSTANTS también. Sin embargo, sin cheque.

class LibraryItem < Object 
    class << self; attr_accessor :attributes; end 
    ATTRIBUTES = ['title', 'authors', 'location',] 
end 

class LibraryBook < LibraryItem 
    ATTRIBUTES .push('ISBN', 'pages'] 
end 
+0

Esto no es lo Yo quiero. Quiero que la variable de instancia de clase para LibraryItem contenga solo ['título', 'autores', 'ubicación',], mientras que la misma variable de instancia para LibraryBook contenga ['título', 'autores', 'ubicación',] más [ 'ISBN', 'páginas']. Editaré la pregunta para aclarar esto. – rlandster

+0

Esto tiene errores de sintaxis. Además, los atributos * del atributo de clase de clase * ni siquiera están conectados a la constante * ATTRIBUTES * de todos modos. –

5

Al igual que una versión:

class LibraryItem < Object 
    def initialize 
    @attributes = ['one', 'two']; 
    end 
end 

class LibraryBook < LibraryItem 
    def initialize 
    super 
    @attributes.push('three') 
end 
end 

b = LibraryBook.new 
+0

Esto es, de hecho, cómo lo estoy haciendo ahora. Pero esta solución no es óptima desde una perspectiva de rendimiento. Establecer los atributos en el método de inicialización significa que el código de configuración de atributos se ejecuta para cada _ objeto creado. Pero los atributos son fijos, así que, al menos teóricamente, debería haber una manera de establecer los atributos una sola vez en el momento de la compilación para cada clase. Reescribiré mi pregunta (nuevamente) para aclarar esto. – rlandster

2

En @attributes variables LibraryBook es una nueva variable independiente, variable de instancia de objeto LibraryBook, así que no es inicializado y se obtiene el error "método no definido ... para nulo"
Usted debe inicializar por la lista de LibraryItem attribut antes de usar

class LibraryBook < LibraryItem 
    @attributes = LibraryItem::attributes + ['ISBN', 'pages'] 
end 
4

por curiosidad, algo así como lo hará ¿este trabajo?

class Foo 
    ATTRIBUTES = ['title','authors','location'] 
end 

class Bar < Foo 
    ATTRIBUTES |= ['ISBN', 'pages'] 
end 

Esto parecería producir el resultado deseado - la matriz ATRIBUTOS se expande cuando se crea el objeto de clase, y los valores de atributos varía como se esperaba:

> Foo::ATTRIBUTES 
=> ['title','authors','location'] 
> Bar::ATTRIBUTES 
=> ['title','authors','location', 'ISBN', 'pages'] 
+1

Quiero que cada instancia de la clase tenga los atributos especificados. Su sugerencia define arrays constantes y no atributos de objetos. – rlandster

+0

Debería ser '|| =' en lugar de '| ='. Pero incluso entonces fallará porque la constante de ATRIBUTOS en Bar aún no está definida, por lo que no puede usar '|| =' en ella. –

+1

@MattConnolly, no, no debería ser '|| ='. 'Array # |' está configurado como union. En el ejemplo, Bar :: ATTRIBUTES se convierte en la unión de Foo :: ATTRIBUTES y el literal de matriz de dos elementos definido en Bar. –

3

ActiveSupport tiene el método class_attribute en el borde de los rieles.

3

Para ampliar la respuesta de @Nick Vanderbilt, al usar active_support, haga esto, que es exactamente la mano corta que quiero para esta funcionalidad. Aquí hay un ejemplo completo:

require 'active_support/core_ext' 

class Foo 
    class_attribute :attributes 
    self.attributes = ['title','authors','location'] 
end 

class Bar < Foo 
    self.attributes = Foo.attributes + ['ISBN', 'pages'] 
end 

puts Foo.attributes.inspect #=> ["title", "authors", "location"] 
puts Bar.attributes.inspect #=> ["title", "authors", "location", "ISBN", "pages"] 

Es una pena que Ruby logre esto sin necesitar una biblioteca para ello. Es lo único que extraño de Python. Y en mi caso, no me importa la dependencia de la gema active_support.

0

Esto es para cuerdas (nada realmente), en lugar de matrices, pero ...

class A 
    def self.a 
    @a || superclass.a rescue nil 
    end 

    def self.a=(value) 
    @a = value 
    end 

    self.a = %w(apple banana chimp) 
end 

class B < A 
end 

class C < B 
    self.a += %w(dromedary elephant) 
end 

class D < A 
    self.a = %w(pi e golden_ratio) 
end 



irb(main):001:0> require 'test2' 
=> true 
irb(main):002:0> A.a 
=> ["apple", "banana", "chimp"] 
irb(main):003:0> B.a 
=> ["apple", "banana", "chimp"] 
irb(main):004:0> C.a 
=> ["apple", "banana", "chimp", "dromedary", "elephant"] 
irb(main):005:0> D.a 
=> ["pi", "e", "golden_ratio"] 
irb(main):006:0> A.a = %w(7) 
=> ["7"] 
irb(main):007:0> A.a 
=> ["7"] 
irb(main):008:0> B.a 
=> ["7"] 
irb(main):009:0> C.a = nil 
=> nil 
irb(main):010:0> C.a 
=> ["7"] 
10

Otra solución sería utilizar el heredaron gancho:

class LibraryItem < Object 
    class << self 
    attr_accessor :attributes 
    def inherit_attributes(attrs) 
     @attributes ||= [] 
     @attributes.concat attrs 
    end 

    def inherited(sublass) 
     sublass.inherit_attributes(@attributes) 
    end 
    end 
    @attributes = ['title', 'authors', 'location',] 
end 

class LibraryBook < LibraryItem 
    @attributes.push('ISBN', 'pages') 
end 
Cuestiones relacionadas