【问题标题】:Ruby class instance variables and inheritanceRuby 类实例变量和继承
【发布时间】:2010-05-02 06:22:12
【问题描述】:

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

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

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

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

最终我会想要几个LibraryItem 的子类,每个子类都有自己的数组@attributes,但每个子类都添加到LibraryItem@attributes(例如,LibraryBookLibraryDVD、@987654333 @ 等)。

所以,这是我的尝试:

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 在其中一个答案中提出的建议)。

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

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

【问题讨论】:

    标签: ruby class instance-variables


    【解决方案1】:

    另一种解决方案是使用 inherited 钩子:

    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
    

    【讨论】:

      【解决方案2】:

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

      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 的只读成员变量,在子类中具有附加值。

      【讨论】:

        【解决方案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'] 
        

        【讨论】:

        • 我希望类的每个实例都具有指定的属性。您的建议定义常量数组而不是对象属性。
        • 应该是||= 而不是|=。但即便如此它也会失败,因为 Bar 中的 ATTRIBUTES 常量尚未定义,因此您不能在其上使用||=
        • @MattConnolly,不应该是||=Array#| 设置并集。在示例中,Bar::ATTRIBUTES 成为 Foo::ATTRIBUTES 和 Bar 中定义的二元素数组字面量的并集。
        【解决方案5】:

        为了扩展@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 gem 的依赖。

        【讨论】:

          【解决方案6】:

          ActiveSupport 在 rails edge 有 class_attribute 方法。

          【讨论】:

            【解决方案7】:

            在LibraryBook变量@attributes是一个新的自变量,对象LibraryBook的实例变量,所以它没有被初始化并且你得到错误“undefined method ... for 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】:

                你也可以使用 CONSTANTS 来做到这一点。虽然没有检查。

                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'、'页数']。我将编辑问题以使其更清楚。
                • 这有语法错误。另外,类对象属性 attributes 甚至都没有连接到 ATTRIBUTES 常量。
                猜你喜欢
                • 2013-08-16
                • 2012-05-30
                • 1970-01-01
                • 1970-01-01
                • 2012-11-05
                • 1970-01-01
                • 2012-12-03
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多