Мне назвали класс Ruby LibraryItem
. Я хочу связать с каждым экземпляром этого класса массив атрибутов. Этот массив долог и смотрит что-то как
['title', 'authors', 'location', ...]
Обратите внимание, что эти атрибуты, как действительно предполагается, не являются методами, просто списком атрибутов это a LibraryItem
имеет.
Затем, я хочу сделать подкласс LibraryItem
названный LibraryBook
это имеет массив атрибутов, который включает все атрибуты LibraryItem
но будет также включать намного больше.
В конечном счете я захочу несколько подклассов LibraryItem
каждый с их собственной версией массива @attributes
но каждое прибавление к LibraryItem
@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 мая 2010), Одно решение этого состоит в том, чтобы сделать @attributes
простая переменная экземпляра и затем добавляет новые атрибуты для LibraryBoot
в initialize
метод (это было предложено демами в одном из ответов).
В то время как это, конечно, работало бы (и, на самом деле, что я делал все время), я не доволен этим, поскольку это является субоптимальным: почему эти неизменные массивы должны быть созданы каждый раз, когда объект создается?
То, что я действительно хочу, должно иметь переменные класса, которые могут наследоваться родительскому классу, но при изменении в дочернем классе не изменяются в родительский класс.
Поскольку вы упомянули, что атрибуты являются «фиксированными» и «неизменными», я предполагаю Вы имеете в виду, что никогда не измените их значение после создания объекта. В этом случае должно работать что-то вроде следующего:
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
, которая имеет дополнительные значения в подклассе.
В LibraryBook переменная @attributes является новой независимой переменной, переменной экземпляра объекта LibraryBook, поэтому она не инициализируется и вы получаете ошибку "undefined method ... for nil"
.
Вы должны инициализировать ее списком атрибутов LibraryItem перед использованием
class LibraryBook < LibraryItem
@attributes = LibraryItem::attributes + ['ISBN', 'pages']
end
Вы также можете сделать это с помощью CINSTANTS. Хотя нет проверки.
class LibraryItem < Object
class << self; attr_accessor :attributes; end
ATTRIBUTES = ['title', 'authors', 'location',]
end
class LibraryBook < LibraryItem
ATTRIBUTES .push('ISBN', 'pages']
end
Как версия:
class LibraryItem < Object
def initialize
@attributes = ['one', 'two'];
end
end
class LibraryBook < LibraryItem
def initialize
super
@attributes.push('three')
end
end
b = LibraryBook.new
Из любопытства, сработает ли что-то подобное?
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']
ActiveSupport имеет метод class_attribute на краю рельсов.