2010-05-02 5 views
17

У меня есть класс Ruby под названием LibraryItem. Я хочу связать с каждым экземпляром этого класса массив атрибутов. Этот массив долго и выглядит какПеременные экземпляра класса Ruby и наследование

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

Обратите внимание, что эти атрибуты не действительно должны быть методами, только список атрибутов, которые LibraryItem имеет.

Далее я хочу сделать подкласс LibraryItem называется LibraryBook, который имеет множество атрибутов, которые включает в себя все атрибуты LibraryItem, но будет также включать в себя многие другие.

В конце концов, я хочу несколько подклассов LibraryItem каждый со своей собственной версией массива @attributes, но каждый из которых добавляет к LibraryItem «s @attributes (например, LibraryBook, LibraryDVD, LibraryMap и т.д.).

Итак, вот моя попытка:

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

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

Это не работает. Я получаю ошибку

undefined method `push' for nil:NilClass 

Если бы работать, я бы хотел что-то вроде этого

puts LibraryItem.attributes 
puts LibraryBook.attributes 

к выходу

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

(Добавлено 02-May-2010) Одно решение к этому нужно сделать @attributes простой переменной экземпляра, а затем добавить новые атрибуты для LibraryBoot в методе initialize (это было предложено демаками в одном o ответы).

Хотя это, безусловно, будет работать (и, фактически, то, что я делаю все это время), я не доволен этим, поскольку он неоптимален: почему эти неизменные массивы должны быть построены каждый раз, когда объект создан?

Я действительно хочу иметь переменные класса, которые могут наследовать родительский класс, но при изменении в дочернем классе не изменяются в родительском классе.

ответ

7

Поскольку вы упоминаете, что атрибуты являются «фиксированными» и «неизменными», я предполагаю, что вы имеете в виду, что вы никогда не измените их значение после создания объекта. В таком случае, что-то вроде следующего должно работать:

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

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

Вы вручную реализует метод чтения (вместо того, чтобы позволить attr_accessor создать его для вас), что маскирует внутреннее имя массива. В вашем подклассе вы просто вызываете функцию читателя класса предка, применяете дополнительные поля, связанные с дочерним классом, и возвращаете это вызывающему. Для пользователя это выглядит как переменная-член только для чтения с именем attributes, которая имеет дополнительные значения в подклассе.

-1

Вы можете сделать это, используя CINSTANTS. Однако не проверяйте.

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

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

Это не то, что Я хочу. Я хочу, чтобы переменная экземпляра класса для LibraryItem содержала только ['title', 'authors', 'location',], в то время как одна и та же переменная экземпляра для LibraryBook содержала ['title', 'authors', 'location',] plus [ 'ISBN', 'pages']. Я отредактирую вопрос, чтобы сделать это более ясным. – rlandster

+0

У этого есть синтаксические ошибки. Кроме того, атрибут * атрибута объекта класса * даже не связан с константой * ATTRIBUTES *. –

5

Так же, как вариант:

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

Это, на самом деле, как я это делаю сейчас. Но это решение не является оптимальным с точки зрения производительности. Установка атрибутов в методе initialize означает, что код установки атрибутов запускается для созданного объекта _every_. Но атрибуты фиксированы, поэтому, теоретически, по крайней мере, должен быть способ установки атрибутов только один раз во время компиляции для каждого класса. Я буду переписывать свой вопрос (снова), чтобы сделать это более ясным. – rlandster

2

В LibraryBook переменной @attributes новая независимая переменная, переменная экземпляра объекта LibraryBook, поэтому его не инициализируется, и вы получите ошибку «неопределенный метод ... для ноля»
Вы должны инициализировать его список LibraryItem attribut перед использованием

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

из любопытства, воли что-то вроде эта работа?

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

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

Казалось бы, для получения желаемого результата - массив ATTRIBUTES расширяется при создании объекта класса, а значения атрибутов изменяется, как ожидалось:

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

Я хочу, чтобы каждый экземпляр класса имел указанные атрибуты. Ваше предложение определяет постоянные массивы, а не атрибуты объекта. – rlandster

+0

Это должно быть '|| =' вместо '| ='. Но даже тогда он не сработает, потому что константа ATTRIBUTES в Bar еще не определена, поэтому вы не можете использовать '|| =' на ней. –

+1

@MattConnolly, нет, это не должно быть '|| ='. 'Array # |' задано объединение. В примере Bar :: ATTRIBUTES становится объединением Foo :: ATTRIBUTES и двухэлементным литералом массива, определенным в Bar. –

3

ActiveSupport имеет метод class_attribute в рельсовых ребрах.

3

Чтобы расширить ответ на @Nick Vanderbilt, используя active_support, вы делаете это, что является именно той короткой рукой, которую я хочу для этой функции. Вот полный пример:

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

Стыдно, что для этого рубина так сложно, что ему не нужна библиотека. Это единственное, что я пропустил от питона. И в моем случае я не против зависимости от свойства active_support.

0

Это для строк (что-нибудь на самом деле), а не массивы, но ...

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

Другим решением было бы использовать унаследовал крюк:

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 
Смежные вопросы