Ruby类实例变量和继承

18

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

['title', 'authors', 'location', ...]
请注意,这些属性实际上不应该是方法,而只是一个LibraryItem拥有的属性列表。
接下来,我想创建一个LibraryItem子类,名为LibraryBook,它具有一个属性数组,其中包括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建议的方法)。

虽然这肯定可以工作(事实上,这就是我一直在做的),但我对此不太满意,因为它是次优的:为什么每次创建对象时都要构造这些不变的数组呢?

我真正想要的是具有继承自父类的类变量,但当在子类中更改时,不会在父类中更改。

9个回答

12

另一个解决方案是使用 继承 钩子:

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

9

鉴于您提到属性是“固定的”和“不变的”,我假设您的意思是一旦对象创建后,您将永远不会更改它们的值。在这种情况下,可以使用以下代码:

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的只读成员变量,其中包含子类中的附加值。

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

事实上,这就是我现在的做法。但从性能角度来看,这种解决方案并不是最优的。在initialize方法中设置属性意味着为每个创建的对象运行属性设置代码。但属性是固定的,因此理论上至少应该有一种方法可以在编译时为每个类仅设置一次属性。我将重新撰写我的问题(再次)以使其更清晰。 - rlandster

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

1
我希望类的每个实例都具有指定的属性。您的建议定义了常量数组而不是对象属性。 - rlandster
应该使用 ||= 而不是 |=。但即使这样,它也会失败,因为 Bar 中的 ATTRIBUTES 常量尚未定义,所以您不能在其上使用 ||= - Matt Connolly
1
@MattConnolly,不应该是 ||=Array#| 是集合并运算。在这个例子中,Bar::ATTRIBUTES 变成了 Foo::ATTRIBUTES 和 Bar 中定义的包含两个元素的数组字面量的并集。 - Clint Pachl

4
为了扩展@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"]

不幸的是,Ruby实现这一点很困难,需要使用库。这是我从Python那里惟一想念的东西。在我的情况下,我不介意依赖于active_support宝石。


3

在Rails的最新版本中,ActiveSupport引入了class_attribute方法。


2
在LibraryBook变量中,@attributes是一个新的独立变量,是LibraryBook对象的实例变量,因此它没有被初始化,你会得到错误“undefined method ... for nil”
在使用之前,您应该通过LibraryItem属性列表对其进行初始化。
class LibraryBook < LibraryItem
  @attributes = LibraryItem::attributes + ['ISBN', 'pages']
end

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

-1

你也可以使用常量来实现,但是没有检查。

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

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

这不是我想要的。我想让LibraryItem类的实例变量仅包含['title','authors','location'],而LibraryBook的同一实例变量则包含['title','authors','location'] 和['ISBN','pages']。我会编辑问题以使其更清晰。 - rlandster
这里有语法错误。此外,类对象属性attributesATTRIBUTES常量没有任何连接。 - Clint Pachl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接