2010-05-02 5 views
17

J'ai une classe Ruby appelée LibraryItem. Je veux associer à chaque instance de cette classe un tableau d'attributs. Ce tableau est long et ressemble à quelque chose commeVariables d'instance de classe Ruby et héritage

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

Notez que ces attributs ne sont pas vraiment censés être des méthodes, juste une liste d'attributs qu'un LibraryItem a.

Ensuite, je veux faire une sous-classe de LibraryItem appelé LibraryBook qui a un tableau d'attributs qui inclut tous les attributs de LibraryItem mais comprendra également beaucoup d'autres.

Finalement je voudrai plusieurs sous-classes de LibraryItem chacun avec leur propre version du tableau @attributes mais chacun ajoutant à l » @attributesLibraryItem (par exemple, LibraryBook, LibraryDVD, LibraryMap, etc.).

Alors, voici ma tentative:

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

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

Cela ne fonctionne pas. Je reçois l'erreur

undefined method `push' for nil:NilClass 

Si elle devait travailler, je veux quelque chose comme ça

puts LibraryItem.attributes 
puts LibraryBook.attributes 

à la sortie

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

(Ajouté 02-mai-2010) Une solution Pour cela, @attributes doit être une variable d'instance simple, puis ajouter les nouveaux attributs pour LibraryBoot dans la méthode initialize (cela a été suggéré par demas dans un o f les réponses). Bien que cela fonctionne certainement (et est, en fait, ce que j'ai fait depuis le début), je ne suis pas satisfait de cela car il est sous-optimal: pourquoi ces tableaux immuables devraient être construits chaque fois qu'un objet est créé? Ce que je veux vraiment, c'est avoir des variables de classe qui peuvent hériter d'une classe parente mais qui ne changent pas dans la classe parent quand elles sont changées dans la classe enfant.

Répondre

7

Puisque vous mentionner que les attributs sont "fixes" et "immuables", je suppose que vous voulez dire que vous ne changerez jamais leur valeur une fois que l'objet est créé. Dans ce cas, quelque chose comme ce qui suit devrait fonctionner:

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

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

Vous implémentez manuellement une méthode de lecture (au lieu de laisser attr_accessor créer pour vous) qui déguise le nom interne du tableau. Dans votre sous-classe, vous appelez simplement la fonction de lecture de la classe d'ancêtres, amorcez sur les champs supplémentaires associés à la classe enfant et renvoyez-la à l'appelant. Pour l'utilisateur, cela ressemble à une variable membre en lecture seule nommée attributes qui a des valeurs supplémentaires dans la sous-classe.

-1

Vous pouvez le faire en utilisant aussi CINSTANTS. Pas de vérification cependant.

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

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

Ce n'est pas ce Je veux. Je veux que la variable d'instance de classe pour LibraryItem contienne uniquement ['title', 'authors', 'emplacement'] alors que la même variable d'instance pour LibraryBook contienne ['title', 'authors', 'location',] plus [ 'ISBN', 'pages']. Je vais éditer la question pour la rendre plus claire. – rlandster

+0

Ceci a des erreurs de syntaxe. De plus, l'attribut d'objet de classe * attributes * n'est même pas connecté à la constante * ATTRIBUTES *. –

5

Tout comme une version:

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

C'est, en fait, comment je le fais maintenant. Mais cette solution n'est pas optimale du point de vue de la performance. La définition des attributs dans la méthode initialize signifie que le code de définition d'attributs est exécuté pour chaque objet créé. Mais les attributs sont fixes, donc, théoriquement au moins, il devrait y avoir un moyen de définir les attributs une seule fois au moment de la compilation pour chaque classe. Je vais réécrire ma question (encore une fois) pour que ce soit plus clair. – rlandster

2

Dans la variable @attributes LibraryBook est une nouvelle variable indépendante, variable d'instance d'objet LibraryBook, de sorte que son non initialisés et vous obtenez une erreur « méthode non définie ... pour nulle »
Vous devez l'initialiser par la liste avant d'utiliser LibraryItem attribut

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

par curiosité, sera quelque chose comme ce travail?

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

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

Cela semble produire le résultat désiré - le tableau ATTRIBUTES est étendu lorsque l'objet de classe est créée, et les valeurs de ATTRIBUTS varie comme prévu:

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

Je veux que chaque instance de la classe ait les attributs spécifiés. Votre suggestion définit des tableaux constants et non des attributs d'objet. – rlandster

+0

Cela devrait être '|| =' au lieu de '| ='. Mais même dans ce cas, cela échouera car la constante ATTRIBUTES dans Bar n'est pas encore définie, donc vous ne pouvez pas utiliser '|| ='. –

+1

@MattConnolly, non ce ne devrait pas être '|| ='. 'Array # |' est défini union. Dans l'exemple, Bar :: ATTRIBUTES devient l'union de Foo :: ATTRIBUTES et le littéral de tableau à deux éléments défini dans Bar. –

3

ActiveSupport a la méthode class_attribute dans le bord des rails.

3

Pour développer la réponse de @Nick Vanderbilt, en utilisant active_support vous faites cela, ce qui est exactement la main courte que je veux pour cette fonctionnalité. Voici un exemple complet:

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"] 

Honte il est si difficile pour les rubis pour y parvenir sans avoir besoin d'une bibliothèque pour elle. C'est la seule chose qui me manque à python. Et dans mon cas, la dépendance à la gemme active_support ne me dérange pas.

0

Ceci est pour cordes (quoi que ce soit vraiment), plutôt que des tableaux, mais ...

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

Une autre solution serait d'utiliser le crochet hérité:

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