Переменные экземпляра класса Ruby и наследование
У меня есть класс Ruby под названием LibraryItem
. Я хочу связать с каждым экземпляром этого класса массив атрибутов. Этот массив длинный и выглядит примерно как
['title', 'authors', 'location', ...]
Обратите внимание, что эти атрибуты на самом деле не должны быть методами, просто список атрибутов, которые имеет 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
(это было предложено демавами в одном из ответов).
Хотя это, безусловно, будет работать (и, фактически, то, что я делаю все это время), я не доволен этим, поскольку он не оптимален: почему эти неизменные массивы должны быть построены каждый раз, когда объект создается
Я действительно хочу иметь переменные класса, которые могут наследоваться от родительского класса, но при изменении в дочернем классе не изменяются в родительском классе.
Ответы
Ответ 1
Поскольку вы упоминаете, что атрибуты "фиксированные" и "неизменные", я предполагаю, что вы имеете в виду, что вы никогда не измените свое значение после создания объекта. В этом случае должно работать следующее:
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
, которая имеет дополнительные значения в подклассе.
Ответ 2
Другим решением будет использование унаследованного hook:
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
Ответ 3
Как версия:
class LibraryItem < Object
def initialize
@attributes = ['one', 'two'];
end
end
class LibraryBook < LibraryItem
def initialize
super
@attributes.push('three')
end
end
b = LibraryBook.new
Ответ 4
Из любопытства будет что-то вроде этой работы?
class Foo
ATTRIBUTES = ['title','authors','location']
end
class Bar < Foo
ATTRIBUTES |= ['ISBN', 'pages']
end
Казалось бы, это приведет к желаемому результату - массив ATTRIBUTES будет расширен при создании объекта класса, а значения ATTRIBUTES будут меняться, как ожидалось:
> Foo::ATTRIBUTES
=> ['title','authors','location']
> Bar::ATTRIBUTES
=> ['title','authors','location', 'ISBN', 'pages']
Ответ 5
ActiveSupport имеет метод class_attribute в рельсовых ребрах.
Ответ 6
Чтобы расширить ответ @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.
Ответ 7
В переменной LibraryBook @attributes - это новая независимая переменная, переменная экземпляра объекта LibraryBook, поэтому ее не инициализируется, и вы получаете ошибку "undefined метод... для nil"
Вы должны инициализировать его списком атрибутов LibraryItem перед использованием
class LibraryBook < LibraryItem
@attributes = LibraryItem::attributes + ['ISBN', 'pages']
end
Ответ 8
Это для строк (что-то действительно), а не массивов, но...
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"]
Ответ 9
Вы можете сделать это, используя CINSTANTS. Однако не проверяйте.
class LibraryItem < Object
class << self; attr_accessor :attributes; end
ATTRIBUTES = ['title', 'authors', 'location',]
end
class LibraryBook < LibraryItem
ATTRIBUTES .push('ISBN', 'pages']
end