2012-05-15 15 views
5

Tôi đã cố gắng thiết lập một hệ thống nhờ đó tôi có thể tạo ra một loạt các lớp Ruby tương tự, được phân biệt bởi tham số nguyên, mà tôi lưu vào một biến lớp của lớp có liên quan - giống như các mẫu C++.Các lớp được định nghĩa động không chính xác chia sẻ dữ liệu - lỗi hoặc lỗi mã hóa?

Tuy nhiên, tham chiếu (do đó, tạo) một phiên bản mới của lớp templated sẽ ghi đè các tham số đã lưu trong các phiên bản trước và tôi không thể giải thích tại sao.

Dưới đây là một ví dụ rất nhỏ

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> 

Tôi đã tìm thấy một lỗi trong Ruby của tôi (ruby 1.9.2p290 (2011/07/09) [i386-mingw32]), hoặc đã tôi chỉ đơn giản là mã hóa một cái gì đó sai?

+0

Tôi đã dành 45 phút để tìm ra điều này, và tôi đoán có điều gì đó sai với các ràng buộc biến vì bạn đang chạy một khối bên trong lớp 'Object'. Tuy nhiên tôi không thể lấy tín dụng cho câu trả lời vì cuối cùng tôi đã tìm thấy lời giải thích như bị nghi ngờ ở đây: http://stackoverflow.com/questions/10109925/ruby-unexpected-results-from-class-exec-when-defining-class- biến. Giải pháp của bạn là chuyển đổi khối đó thành chuỗi và đánh giá nó thay vì vậy nó sẽ được biên dịch trong ngữ cảnh đúng. – Casper

+0

@Casper khi câu trả lời là "sử dụng chuỗi eval thay vì khối eval", nó thường là thời gian để tìm kiếm một câu trả lời tốt hơn. Chuỗi eval là nguy hiểm và mong manh, đôi khi đó là những gì bạn cần, nhưng tôi ước tính hơn 95% thời gian tôi thấy nó được sử dụng có một hình thức ít nguy hiểm có sẵn. – dbenhur

Trả lời

1

Bởi vì lần đầu tiên bạn tham khảo cú pháp @@base trong bối cảnh của lớp Object, đó là một biến lớp học của đối tượng và tất cả các lớp con TemplX của đối tượng tham khảo var lớp của lớp cha. Bạn có thể thay đổi mã của mình để sử dụng Module#class_variable_setclass_variable_get để tránh ràng buộc trong lớp cha.

Một số vấn đề khác với mã của bạn: Tôi lưu ý rằng bạn không thực hiện make_templ một phương thức lớp ngang hàng self.const_missing, mặc dù nó được gửi thành công vì Object là tổ tiên của Lớp. Tốt nhất là tránh tất cả các dạng eval (string) khi các phương thức khác tồn tại. Bạn không nên tăng NameError nếu bạn không xử lý const_missing, nhưng thay vì gửi đến siêu như một người khác có thể nằm trong chuỗi và muốn làm điều gì đó để giải quyết hằng số.

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 

Biến lớp có thông tin thú vị và không mong muốn khi trộn thuộc tính thông qua kế thừa. Bạn đã nhấn một trong số các gotchas. Tôi không biết các thuộc tính khác mà bạn cần xung quanh @@base, nhưng có vẻ như bạn sẽ nhận được sự cô lập tốt hơn và ít kết quả đáng ngạc nhiên hơn bằng cách sử dụng một biến cá thể lớp thay thế. Để giải thích thêm: Fowler, RailsTips

+0

Nếu tôi có thể +1 bạn nhiều hơn một lần! Tôi sẽ sử dụng các biến thể hiện lớp, tôi nghĩ, nhưng các mẹo khác của bạn cũng là vàng.Tôi đã nghĩ rằng 'super' trong' const_missing' sẽ hoạt động, vì 'Object' không định nghĩa' const_missing'; và tôi sẽ không tìm ra cách tạo tên lớp động mà không có 'eval', do đó, điều đó thực sự tuyệt vời. Chúc mừng! – Chowlett

1

Nhận xét từ @Casper giúp chỉ ra lý do mã của bạn không hoạt động. Để khắc phục, hãy xem xét sử dụng biến mẫu lớp thay vì biến lớp. Điều này sẽ giúp bạn tránh được việc phải eval và né tránh các cạm bẫy phổ biến của việc sử dụng các biến lớp:


EDIT: thêm refactoring từ @dbenhur, chuyển biến lớp học để biến lớp ví dụ.

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 để đề xuất các cá thể lớp, -1 vì không giải quyết các vấn đề khác với mã OPs (chuỗi không cần thiết eval, không có siêu trên đường dẫn khác của const_missing) – dbenhur

Các vấn đề liên quan