关于ruby类变量的困惑

时间:2014-02-20 12:00:51

标签: ruby class-variables

使用类变量

假设一个简单的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"不会影响类中的变量。

2 个答案:

答案 0 :(得分:6)

@@varHolder的类变量。并且顶级的@@var@@var的同名Holder的类变量不同,您正在为类Object创建一个全新的类变量。现在@@var与父类的子类共享。 Object是类Holder的父类。在Ruby中,如果没有显式定义任何自定义类的超类,则使用class关键字进行定义,然后Object类成为您刚刚创建的类的默认超类。 / p>

在顶层,您在类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类中的相同名称,使用这个新名称,您刚刚定义在Object class。范围内。

请记住,类变量是共享变量,仅对类(B)可见,您在其中定义了它及其后代类(C),后代({ {1}})后代类(D),依此类推。但是在类(C如下所示)之前的超类A)不可见,直到您在父类中定义了相同的名称变量({ {1}}),就像你在子类(B)中一样。

A

答案 1 :(得分:1)

我想扩大其他人所说的内容,特别是比较类变量与类实例变量的使用。让我们从这开始:

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 =&gt;创建方法时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 << selfselfHolder2更改为Holder2的单例类(也称为元类),直到下一个end。请注意,Ruby将Holder2的单例类表示为#<Class:Holder2>。此外,我们不需要在my_dog前加self.,因为创建方法时selfHolder2的单例类。

请注意:

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

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

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

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

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