5

我一直在尝试建立一个系统,借此我可以生成一系列类似的 Ruby 类,这些类由一个整数参数区分,我将其保存到相关类的类变量中——类似于 C++ 模板。

但是,引用(因此,创建)模板类的新版本会覆盖以前版本中保存的参数,我不知道为什么。

这是一个最小的例子

class Object
  def self.const_missing(name)
    if name =~ /^Templ(\d+)$/
      return make_templ $1.to_i
    else
      raise NameError.new("uninitialised constant #{name}")
    end
  end

private
  def make_templ(base)
    # Make sure we don't define twice
    if Object.const_defined? "Templ#{base}"
      return Object.const_get "Templ#{base}"
    else
      # Define a stub class
      Object.class_eval "class Templ#{base}; end"

      # Open the class and define the actual things we need.
      Object.const_get("Templ#{base}").class_exec(base) do |in_base|        
        @@base = in_base

        def initialize
          puts "Inited with base == #{@@base}"
        end
      end

      Object.const_get("Templ#{base}")
    end
  end
end

irb(main):002:0> Templ1.new
Inited with base == 1
=> #<Templ1:0x26c11c8>
irb(main):003:0> Templ2.new
Inited with base == 2
=> #<Templ2:0x20a8370>
irb(main):004:0> Templ1.new
Inited with base == 2
=> #<Templ1:0x261d908>

我是否在我的 Ruby 中发现了一个错误(ruby 1.9.2p290 (2011-07-09) [i386-mingw32]),还是我只是编码错误?

4

2 回答 2

1

@Casper 的评论有助于指出您的代码无法正常工作的原因。要解决此问题,请考虑使用类实例变量而不是类变量。这应该可以帮助您避免eval使用类变量的常见陷阱:


编辑:从@dbenhur 添加重构,将类变量切换到类实例变量。

class Object
  def self.const_missing(name)
    name =~ /^Templ(\d+)$/ ? make_templ($1.to_i) : super
  end

private
  def self.make_templ(base)
    klass_name = "Templ#{base}"
    if const_defined? klass_name
      const_get klass_name
    else
      klass = Class.new(Object) do
        class << self
          attr_accessor :base
        end
        self.base = base
        def initialize
          puts "Inited with base == #{self.class.base}"
        end
      end
      const_set klass_name, klass    
    end
  end
end

puts Templ1.new.class.base
# => Inited with base == 1
# => 1
puts Templ2.new.class.base
# => Inited with base == 2
# => 2
puts Templ1.new.class.base
# => Inited with base == 1
# => 1
于 2012-05-15T17:53:23.680 回答
1

因为您首先在类 Object 的上下文中进行语法引用@@base,所以它是 Object 的类变量,并且 object 的所有 TemplX 子类都引用超类的类 var。您可以更改代码以使用Module#class_variable_setclass_variable_get避免超类中的绑定。

您的代码的其他一些问题:我注意到您没有使make_templ类方法对等self.const_missing,尽管它成功调度,因为 Object 是 Class 的祖先。当存在其他方法时,最好避免使用所有形式的 eval(string)。如果您不处理 const_missing,则不应引发 NameError,而是分派给 super,因为其他人可能在链中并想要做一些事情来解决常量。

class Object
  def self.const_missing(name)
    if name =~ /^Templ(\d+)$/
      return make_templ $1.to_i
    end
    super
  end

private
  def self.make_templ(base)
    klass_name = "Templ#{base}"
    unless const_defined? klass_name
      klass = Class.new(Object) do
        class_variable_set :@@base, base
        def initialize
          puts "Inited with base == #{self.class.class_variable_get(:@@base)}"
        end
      end
      const_set klass_name, klass    
    end

    const_get klass_name
  end
end

类变量通过继承具有有趣且通常不受欢迎的信息混合属性。你遇到了其中一个问题。我不知道您还需要什么其他属性@@base,但看起来您可能会使用类实例变量获得更好的隔离和更少令人惊讶的结果。更多解释:FowlerRailsTips

于 2012-05-15T18:04:17.683 回答