【问题标题】:Initializing class instance variables in Ruby在 Ruby 中初始化类实例变量
【发布时间】:2011-06-21 12:49:29
【问题描述】:

我正在开发一个小型 Rails 应用程序,但 ruby​​ 的 OOP 模型存在问题。我有以下简化的类结构。

class Foo
  protected
    @bar = []
    def self.add_bar(val)
      @bar += val
    end
    def self.get_bar
      @bar
    end
end

class Baz < Foo
  add_bar ["a", "b", "c"]
end

我现在的问题是,当我在 Baz 的类定义中调用 add_bar 时,@bar 显然没有初始化,并且我收到一个错误,即 + 运算符不适用于 nil。直接在Foo 上调用add_bar 不会产生此问题。为什么会这样?如何正确初始化@bar

为了明确我想要什么,我将指出我期望从这些类中获得的行为。

Foo.add_bar ["a", "b"]
Baz.add_bar ["1", "2"]
Foo.get_bar # => ["a", "b"]
Baz.get_bar # => ["a", "b", "1", "2"]

我怎样才能做到这一点?

【问题讨论】:

    标签: ruby-on-rails ruby oop


    【解决方案1】:

    简答:实例变量不会被子类继承

    更长的答案:问题是你在类的主体中写了@bar = [](在任何方法之外)。当您设置一个实例变量时,它存储在当前self 上。当您在类主体中时,self 是类对象 Foo。因此,在您的示例中,@foo 在类对象 Foo 上定义。

    稍后,当您尝试查找实例变量时,Ruby 会查找当前为 self 的任何内容。当您从 Baz 调用 add_bar 时,self 是 Baz。 self 在 add_bar 的主体中仍然是 Baz(即使该方法在 Foo 中)。因此,Ruby 在 Baz 中查找 @bar 并找不到它(因为您在 Foo 中定义了它)。

    这里有一个例子可以更清楚地说明这一点

    class Foo
      @bar = "I'm defined on the class object Foo. self is #{self}"
    
     def self.get_bar
        puts "In the class method. self is #{self}"    
        @bar
      end
    
      def get_bar
        puts "In the instance method. self is #{self} (can't see @bar!)"
        @bar
      end
    end
    
    >> Foo.get_bar
    In the class method. self is Foo
    => "I'm defined on the class object Foo. self is Foo"
    
    >> Foo.new.get_bar
    In the instance method. self is #<Foo:0x1056eaea0> (can't see @bar!)
    => nil
    

    这确实有点令人困惑,对于刚接触 Ruby 的人来说也是一个常见的绊脚石,所以不要难过。当我阅读Programming Ruby(又名“The Pickaxe”)中的“元编程”一章时,我终于明白了这个概念。

    我将如何解决您的问题:查看 Rails 的 class_attribute 方法。它允许您尝试做的事情(在父类上定义一个可以在其子类中继承(和覆盖)的属性)。

    【讨论】:

    • 我必须使用的 rails 2.3.* 似乎没有 ActiveSupport 中的 class_attribute 功能,但 class_inheritable_accessor 似乎完全符合我的要求,所以我现在正在使用它。感谢您的提示。
    【解决方案2】:

    好吧,因为@bar 被定义为类实例变量,所以它仅限于类 Foo。 检查这个:

    class Foo
        @bar = []
    end
    
    class Baz < Foo
    end
    
    Foo.instance_variables #=> [:@bar]
    Baz.instance_variables #=> []
    

    无论如何,对于这个简单的示例,您可以这样做:

    class Foo
      protected
        def self.add_bar(val)
          @bar ||=[]
          @bar += val
        end
        def self.get_bar
          @bar
        end
    end
    
    class Baz < Foo
      add_bar ["a", "b", "c"]
    end
    

    阅读更多关于here的信息。

    【讨论】:

      【解决方案3】:

      我是这样做的:

      class Base
      
          class << self
      
              attr_accessor :some_var
      
      
              def set_some_var(value)
                  self.some_var = value
              end
      
          end
      
      end
      
      
      class SubClass1 < Base
        set_some_var :foo
      end
      
      class SubClass2 < Base
        set_some_var :bar
      end
      

      那么它应该做你想做的事。

      [8] pry(main)> puts SubClass1.some_var
      foo
      [9] pry(main)> puts SubClass2.some_var
      bar
      

      请注意,set_some_var 方法是可选的,如果您愿意,可以使用SubClass1.some_var = ...

      如果您想要一些默认值,请在 class &lt;&lt; self 下添加类似的内容

      def some_var
          @some_var || 'default value'
      end
      

      【讨论】:

        【解决方案4】:

        这似乎运作良好:

        class Foo
          protected
          @@bar = {}
          def self.add_bar(val)
            @@bar[self] ||= []
            @@bar[self] += val
          end
          def self.get_bar
            (self == Foo ? [] : @@bar[Foo] || []) + (@@bar[self] || [])
          end
        end
        class Baz < Foo
        end
        
        Foo.add_bar ["a", "b"]
        Baz.add_bar ["1", "2"]
        Foo.get_bar     # => ["a", "b"]
        Baz.get_bar     # => ["a", "b", "1", "2"]
        

        【讨论】:

        • 是的,我也尝试过,但问题是,@@bar 变量对于类和它的子类是全局的,这也不是我想要的。我将编辑我的问题,以明确我想要什么。
        猜你喜欢
        • 2017-04-05
        • 2017-10-23
        • 2011-12-25
        • 2010-10-16
        • 2013-09-21
        • 2012-09-17
        • 2023-03-14
        • 1970-01-01
        • 2021-08-12
        相关资源
        最近更新 更多