Ruby类实例变量和inheritance

我有一个名为LibraryItem的Ruby类。 我想将此类的每个实例与一组属性相关联。 这个数组很长,看起来像

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

请注意,这些属性实际上不应该是方法,只是LibraryItem具有的属性列表。

接下来,我想创建一个名为LibraryBookLibraryItem的子类,它具有一个属性数组,其中包含LibraryItem所有属性,但也包含更多属性。

最后,我将需要LibraryItem几个子类,每个子类都有自己的数组@attributes但每个子类都添加到LibraryItem@attributes (例如, LibraryBookLibraryDVDLibraryMap等)。

所以,这是我的尝试:

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

(2010年5月2日添加)对此的一个解决方案是使@attributes成为一个简单的实例变量,然后在initialize方法中添加LibraryBoot的新属性(这是demas在其中一个答案中建议的)。

虽然这肯定会起作用(事实上,我一直在做的事情),但我对此并不满意,因为它是次优的:为什么每次创建一个对象时都要构造这些不变的数组?

我真正想要的是拥有可以从父类inheritance的类变量,但是在子类中更改时不会在父类中更改。

既然你提到属性是“固定的”和“不变的”,我假设你的意思是你创建对象后永远不会改变它们的值。 在这种情况下,类似下面的东西应该工作:

 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为您创建它),它伪装了数组的内部名称。 在您的子类中,您只需调用祖先类的reader函数,添加与子类关联的其他字段,并将其返回给调用者。 对于用户来说,这看起来像一个名为attributes的只读成员变量,在子类中有其他值。

另一个解决方案是使用inheritance的钩子:

 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 

就像一个版本:

 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数组,并且ATTRIBUTES的值会按预期变化:

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

ActiveSupport在rails edge中具有class_attribute方法。

为了扩展@Nick Vanderbilt的答案,使用active_support你可以做到这一点,这正是我想要的这个function的简短。 这是一个完整的例子:

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

遗憾的是,ruby很难实现这一目标而不需要库。 这是我唯一想念python的东西。 在我的情况下,我不介意对active_support gem的依赖。

在LibraryBook变量中@attributes是一个新的自变量,对象LibraryBook的实例变量,所以它没有被初始化,你得到错误“undefined method … for nil”
在使用之前,您应该通过LibraryItem attribut的列表对其进行初始化

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

这是为了字符串(任何真的),而不是数组,但……

 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> Aa => ["apple", "banana", "chimp"] irb(main):003:0> Ba => ["apple", "banana", "chimp"] irb(main):004:0> Ca => ["apple", "banana", "chimp", "dromedary", "elephant"] irb(main):005:0> Da => ["pi", "e", "golden_ratio"] irb(main):006:0> Aa = %w( 7 ) => ["7"] irb(main):007:0> Aa => ["7"] irb(main):008:0> Ba => ["7"] irb(main):009:0> Ca = nil => nil irb(main):010:0> Ca => ["7"] 

您也可以使用CINSTANTS来完成。 虽然没有检查。

 class LibraryItem < Object class << self; attr_accessor :attributes; end ATTRIBUTES = ['title', 'authors', 'location',] end class LibraryBook < LibraryItem ATTRIBUTES .push('ISBN', 'pages'] end