我一直试图建立一个系统,我可以生成一系列类似的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]),还是我编写了错误的错误?
答案 0 :(得分: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
答案 1 :(得分:1)
因为你首先在Object类的上下文中语法引用@@base
,所以它是Object的一个类变量,而object的所有TemplX子类都引用了超类的类var。您可以更改代码以使用Module#class_variable_set和class_variable_get
来避免超类中的绑定。
您的代码的一些其他问题:我注意到您没有使make_templ
成为self.const_missing
的类方法对等,尽管它成功调度,因为Object是Class的祖先。当存在其他方法时,最好避免使用所有形式的eval(字符串)。如果你不处理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
周围使用什么其他属性,但看起来你可能会使用类实例变量来获得更好的隔离和更少惊人的结果。有关详细说明,请执行以下操作:Fowler,RailsTips