Ruby:继承使用类变量的代码

时间:2009-08-09 13:12:42

标签: ruby class inheritance metaprogramming

情况:我有多个类,每个类都应该包含一个带有配置哈希的变量;每个类的不同哈希,但对于类的所有实例都是相同的。

起初,我试过这样的

class A
  def self.init config
    @@config = config
  end

  def config
    @@config
  end
end

class B < A; end
class C < A; end

但很快就注意到它不会那样工作,因为@@ config是在A的上下文中保存,而不是B或C,因此:

B.init "bar"
p B.new.config  # => "bar"
p C.new.config  # => "bar" - which would be nil if B had it's own @@config

C.init "foo"
p B.new.config  # => "foo" - which would still be "bar" if C had it's own @@config
p C.new.config  # => "foo"

我想这样用它:

modules = [B, C]
modules.each do |m|
  m.init(@config[m.name])
end
# ...
B.new  # which should then have the correct config

现在,我很清楚为什么会这样,但我不确定它是这样的原因。

它是否也能以其他方式工作,将类变量保存在子类的上下文中?

我还发现令人恼火的事实是,即使在超级类中被称为“自我”,它也总是子类。从这开始,我首先期望来自超类的代码“在子类的上下文中执行。

对此的一些启示将不胜感激。

另一方面,我可能不得不接受它的工作方式,我必须找到另一种方法来做到这一点。

有没有“元”方式来做到这一点? (我尝试过class_variable_set等,但没有运气)

或许这个'init'方法的整个想法可能首先存在缺陷,还有其他一些“模式”可以做到这一点吗?

我可以让@@ config一个哈希,拿着所有的配置并总是选择正确的一个,但我发现有点尴尬......(那里不是继承来解决这类问题吗?)

1 个答案:

答案 0 :(得分:105)

@@variables不是类变量。它们是类层次结构变量,即它们在整个类层次结构之间共享,包括所有子类和所有子类的所有实例。 (有人建议人们应该认为@@variables更像$$variables,因为它们实际上与$globals有更多共同点,而不是@ivars。这样可以减少混淆。其他人已经走得更远,并建议他们应该从语言中删除。)

Ruby在某种意义上没有类变量,比如Java(它们被称为静态字段)具有它们。它不需要需要类变量,因为类也是对象,因此它们可以像任何其他对象一样拥有实例变量。您所要做的就是删除无关的@。 (并且您必须为类实例变量提供一个访问器方法。)

class A
  def self.init config
    @config = config
  end

  def self.config # This is needed for access from outside
    @config
  end

  def config
    self.class.config # this calls the above accessor on self's class
  end
end

让我们稍微简化一下,因为A.config显然只是一个attribute_reader:

class A
  class << self
    def init config
      @config = config
    end

    attr_reader :config
  end

  def config
    self.class.config
  end
end

事实上,A.init只是一个有趣名字的作家,所以我们将其重命名为A.config=并使其成为作家,这反过来意味着我们的方法现在只是访问者对。 (由于我们更改了API,显然测试代码也必须更改。)

class A
  class << self
    attr_accessor :config
  end

  def config
    self.class.config
  end
end

class B < A; end
class C < A; end

B.config = "bar"
p B.new.config  # => "bar"
p C.new.config  # => nil

C.config = "foo"
p B.new.config  # => "bar"
p C.new.config  # => "foo"

然而,如果你需要这个,我无法摆脱对设计有更根本的不确定感的感觉。