Ruby类变量

22

Ruby的类实例化让我头疼。我知道在给定这个...

class Foo
  @var = 'bar'
end

...@var是创建类实例的变量。

但是我如何创建可重写的子类变量?

这是一个在Python中的示例:

class Fish:
var = 'fish'
def v(self):
    return self.var

class Trout(Fish):
    var = 'trout'

class Salmon(Fish):
    var = 'salmon'

print Trout().v()
print Salmon().v()

输出结果为:

trout
salmon

在Ruby中如何做同样的事情?


1
第一个代码块中应该使用@@var,是吗? - Jean
我正在阅读David Black所著的《The Well-Grounded Rubyist》(http://manning.com/black2/)。他在这个领域解释所有细微差别的工作做得非常出色。 - glenn jackman
1
Jean: 不行。如果我使用 @@var,那么子类将会覆盖父类。请参考hobodave的链接。特别是例子中的'2'是如何被覆盖的。 - docwhat
5个回答

21
为了与@khelll的答案形成对比,这里使用了类对象上的实例变量:
class Fish
  # an instance variable of this Class object
  @var = 'fish'

  # the "getter"
  def self.v
    @var
  end

  # the "setter"
  def self.v=(a_fish)
    @var = a_fish
  end
end

class Trout < Fish
  self.v = 'trout'
end

class Salmon < Fish
  self.v = 'salmon'
end

p Trout.v   # => "trout"
p Salmon.v  # => "salmon"

编辑:让实例可以读取类的实例变量的示例:

class Fish
  def type_of_fish
    self.class.v
  end
end

p Trout.new.type_of_fish   # => "trout"
p Salmon.new.type_of_fish  # => "salmon"

不完全正确。你在结尾处并没有使用 Trout.new 或 Salmon.new。你正在使用类本身。我想让该实例获得类变量。 - docwhat
@The Doctor -- 现在怎么样? - glenn jackman
是的。我认为现在它在功能上等同于我给出的答案。你只是手动编写访问器。 - docwhat
需要编写访问器(accessors):所有实例变量都是私有的,并且 attr_* 方法适用于实例对象而不是类对象。 - glenn jackman

8

@var上面提到的被称为类实例变量,与实例变量不同...阅读回答此处以查看差异。

无论如何,这是等效的Ruby代码:

class Fish
  def initialize
    @var = 'fish'
  end

  def v
    @var
  end
end

class Trout < Fish
  def initialize
    @var = 'trout' 
  end
end

class Salmon < Fish
  def initialize
    @var = 'salmon' 
  end
end

puts Trout.new.v
puts Salmon.new.v

这并不是真正的问题,因为每次子类化时都必须覆盖初始化。这并不是非常实用的。在我的示例中包含一些代码可能会更好。 - docwhat

4

这是我最终通过hobodave的链接找到的版本:

class Fish
  class << self
    attr_accessor :var
  end

  @var = 'fish'
  def v
    self.class.var
  end
end

class Trout < Fish
  @var = 'trout'
end

class Salmon < Fish
  @var = 'salmon'
end

puts (Trout.new).v   # => trout
puts (Salmon.new).v  # => salmon

请注意,子类化只需要添加一个@var,无需覆盖initialize方法。

2

这是Java程序员转向Ruby时经常犯的错误之一,也是我必须克服的一个重大概念跳跃。起初似乎很奇怪,但这实际上是Ruby中更酷的方面之一——所有代码都是可执行的,包括类定义。

因此,实例变量必须在方法内声明。这与如何评估“self”有关。“self”是当前对象。解释器首先会在“self”中查找方法调用和变量引用:

class Fish
    @var = "foo" # here 'self' == Fish, the constant which contains the class object  
    def foo
        # do foo
    end
end

fish = Fish.new
fish.foo # here 'self' == fish, an instance of Fish

在类定义中,“self”被设置为正在定义的类对象,因此类定义内的任何引用都将引用该类对象,在这种情况下为Fish。
然而,当在Fish实例上调用方法时,“self”被设置为调用接收者,即Fish的特定实例。因此,在方法定义之外,“self”是类对象。在方法内部,“self”是接收者的实例。这就是为什么方法定义之外的“@var”更像Java中的静态变量,而方法定义内的“@var”是实例变量的原因。

笔误:fish.foo 应为 fish.var - docwhat
1
实际上并不是。我没有定义'foo',但重点在于Ruby解释器会将'fish'视为调用的接收者,并将'self'设置为'fish'以解决引用。虽然为了清晰起见,我已经添加了foo方法。调用'fish.var'将抛出一个NoMethodError异常。 - Dave Sims

1

有一个问题:你可以覆盖@var:
Salmon.var = 'shark' 将覆盖@var,因此
puts (Salmon.new).v # => shark


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