【问题标题】:Confusion about ruby class variable关于 ruby​​ 类变量的困惑
【发布时间】:2014-03-21 08:37:01
【问题描述】:

假设一个使用类变量的简单 ruby​​ 程序,

    class Holder
      @@var = 99
      def Holder.var=(val)
        @@var = val
      end
      def var
        @@var
      end
    end

    @@var = "top level variable"

    a = Holder.new
    puts a.var

我猜结果应该是99,但输出不是99。我想知道为什么。由于类变量的作用域是类,我假设@@var = "top level variable" 行不会影响类中的变量。

【问题讨论】:

    标签: ruby class-variables


    【解决方案1】:

    @@varHolder 的类变量。而顶层的@@var 不是Holder 的同名@@var 的同一个类变量,它是你正在为类Object 创建一个全新的类变量。现在@@var 与父类的子类共享。 Object 是类Holder 的父类。在 Ruby 中,如果您没有显式定义任何自定义类的超类,而是使用 class 关键字定义,那么 Object 类将成为您刚刚创建的类的默认超类。

    在顶层,您在 Object 类中定义了一个新的类变量,就像您在发布的示例中所做的那样。通过继承,它现在可用于类Holder

    我的意思是,当你写以下内容时:

    class Holder
      @@var = 99
      def Holder.var=(val)
        @@var = val
      end
      def var
        @@var
      end
    end
    

    @@var 尚未添加到 Object 类中。现在在您编写以下行时位于顶层:

    @@var = "top level variable"
    

    这意味着,您将其添加到 Object 类并更新旧变量 (@@var) 与 Holder 类一中的名称相同,使用这个新变量,您刚刚在 @987654341 的范围内定义@类。

    记住,类变量是共享变量,只对你定义它的类(B)和它的后代类(C)、后代(D)可见后代类(C),依此类推。但类(B 如下)的超类A 如下)不可见,直到您在父类中定义了同名变量(A),就像你在子班里一样(B)。

    class A 
    end
    
    class B < A
      @@var = 10 
    end
    
    class C < B 
    end
    
    class D < C 
    end
    
    A.class_variable_get(:@@var) # uninitialized class variable @@var in A (NameError)
    B.class_variable_get(:@@var) # 10
    C.class_variable_get(:@@var) # 10
    D.class_variable_get(:@@var) # 10
    

    【讨论】:

      【解决方案2】:

      我想详细说明其他人所说的话,特别是比较类变量的使用与类实例变量。让我们从这个开始:

      class Holder1
      end
      
      class Holder2 < Holder1
        @@var = 99
      
        # Create a class setter and a getter for the class variable
        def Holder2.var=(val)
          @@var = val
        end
      
        def Holder2.var
          @@var
        end
      
        # Create a instance getter for the class variable
        def var
          @@var
        end
      end
      
      class Holder3 < Holder2
      end
      
      Holder2.var     #=> 99
      Holder2.var = 1
      Holder3.var     #=> 1
      Holder1.var     #=> NoMethodError: undefined method `var' for Holder1:Class
      Holder2.new.var #=> 1
      Holder3.new.var #=> 1
      
      Holder3.var = 2
      Holder3.var     #=> 2
      Holder2.var     #=> 2
      Holder3.new.var #=> 2
      Holder2.new.var #=> 2
      

      另外:前两种方法通常会这样写:

        def self.var=(val)
          @@var = val
        end
      
        def self.var
          @@var
        end
      

      这是有效的,因为 self => Holder2 在创建方法时。通过不硬连线类名,如果您决定重命名类,则不需要更改。

      现在这(使用类变量)可能正是您想要的行为。也就是说,如果您希望子类看到变量并能够更改它,那么您需要一个类变量。

      但是,如果您希望每个子类都有自己的变量,子类既不能看到也不能更改该变量,则需要使用 类实例变量@var,而不是类变量,@@var。 (从技术上讲,这并不完全正确,因为可以在程序中的任何位置使用 Holder2.instance_variable_get(:@var)Holder2.instance_variable_set(:@var)。)

      将下面代码的结果与上面的结果进行比较。我包含了一个与类实例变量@var 同名的实例变量,以说明它们与@night@day 一样不同。

      class Holder1
      end
      
      class Holder2 < Holder1
        # Create an accessor for the instance variable
        attr_accessor :var
      
        # Initialize class instance variable
        @var = 99
      
        # Create an accessor for the class instance variable
        class << self
          puts "self in 'class << self': #{self}"
          attr_accessor :var
          def our_dog
            "Diva"
          end
        end
      
        # Create a class method
        def self.our_cat
          puts "self in 'self.our_cat())' def: #{self}"
          "Teagan"
        end   
      
        # Create an instance setter and a getter for the class instance variable
        def c_ivar=(val)
          self.class.var = val
        end
      
        def c_ivar
          self.class.var
        end
      end
      
      class Holder3 < Holder2
      end
      
                       #=> self in 'class << self': #<Class:Holder2>
      
      Holder2.var      #=> 99
      h2 = Holder2.new
      h2.var           #=> nil
      
      Holder2.var = 1
      Holder2.var      #=> 1
      h2.c_ivar        #=> 1
      h2.c_ivar = 2
      Holder2.var      #=> 2
      
      h2.var           #=> nil
      h2.var = 3
      h2.var           #=> 3
      Holder2.var      #=> 2
      
      Holder3.var      #=> nil
      Holder1.var      #=> NoMethodError: undefined method `var' for Holder1:Class
      
      Holder3.var = 4
      Holder3.var      #=> 4
      Holder2.var      #=> 2
      h3 = Holder3.new
      h3.c_ivar        #=> 4 
      h2.c_ivar        #=> 2
      
      Holder3.our_dog  #=> "Diva"
      Holder3.our_cat  #=> "self in 'self.our_cat())' def: Holder3"
                       #=> "Teagan"
      

      Holder1.var 引发异常,因为该类无权访问Holder2 的类实例变量var@。相比之下,第一次使用 Holder3.var 会返回 nil,因为通过继承存在 Holder3 的变量,但尚未初始化。

      表达式class &lt;&lt; selfselfHolder2 更改为Holder2 的单例类(又名元类),直到下一个end。请注意,Ruby 将Holder2 的单例类表示为#&lt;Class:Holder2&gt;。此外,我们不需要在my_dog 前面加上self.,因为self 在创建方法时是Holder2 的单例类。

      注意:

      Holder2.singleton_methods #=> [:var, :var=, :our_dog, :our_cat]
      

      这表明our_cat()Holder2 的单例类的方法,即使selfHolder2(而不是Holder2 的单例类',#&lt;Class:Holder2&gt;)。建。出于这个原因,有人说,从技术上讲,"there is no such thing as a class method"。这也告诉我们,我们可以将my_cat 的定义移到class &lt;&lt; self 构造中(并删除self.)。

      另一种向Holder2的单例类添加实例变量和方法的常用方法是将class &lt;&lt; self ... end替换为extend M并创建一个模块:

      module M
        attr_accessor :var
        def our_dog
          "Diva"
        end
      end 
      

      Object#extend 将这些实例变量和方法混合到Holder2 的单例类中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-04-09
        • 1970-01-01
        • 1970-01-01
        • 2013-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多