2012-05-15 16 views
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中的錯誤(紅寶石1.9.2p290(2011-07-09)[I386-的mingw32]),或有我只是編碼出錯了?

+0

嗯,我花了45分鐘試圖算出這個,我猜,因爲你正在運行的'Object'類中的塊的東西是錯的變量綁定。然而,我不能拿信用的答案,因爲我終於找到了解釋懷疑在這裏:http://stackoverflow.com/questions/10109925/ruby-unexpected-results-from-class-exec-when-defining-class-變量。您的解決方案是將該塊轉換爲字符串並對其進行評估,以便將其編譯到正確的上下文中。 – Casper

+0

@Casper當答案是「使用字符串評估而不是塊評估」時,通常是搜索更好的答案的時候。字符串評估是危險和脆弱的,有時候是你需要的,但是我估計有超過95%的時間我看到它被使用,這是一種不太危險的表單。 – dbenhur

回答

1

因爲你第一語法引用類對象的上下文@@base,它是對象的類變量,所有對象的TemplX子類指的是超類的類變種。您可以更改代碼以使用Module#class_variable_setclass_variable_get來避免超類中的綁定。

您的代碼還有其他一些問題:我注意到您沒有使make_templ成爲self.const_missing的類方法同級,儘管它已成功調度,因爲Object是Class的祖先。當其他方法存在時,最好避免所有形式的eval(字符串)。如果您不處理const_missing,則不應該引發NameError,而應該派遣到超級,因爲其他人可能在鏈中並想要解決常量。

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

+0

如果我可以給你+1以上,我會!我想我會使用類實例變量,但是您的其他提示也很有用。因爲'Object'沒有定義'const_missing',所以我認爲'const_missing'中的'super'會起作用。我不知道如何創建不帶'eval'的動態類名,所以這真的很酷。乾杯! – Chowlett

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 
+0

+1表示類實例變量,-1表示不解決OPs代碼的其他問題(不必要的字符串eval,在const_missing的其他路徑上沒有超類) – dbenhur