【发布时间】:2009-12-14 16:21:34
【问题描述】:
我用 Ruby 类动态加载/卸载/更新作为实现插件的基础设施做了一些实验。我发现了几点:
- 如果加载同一类的新版本而不首先卸载它,则新版本本质上是“顶部”或“合并”与以前的版本。使用先前版本创建的所有现有对象都将“更新”其类定义。
- 卸载类不会影响使用该类创建的现有对象。现有对象保留在刚刚卸载的任何版本中。 (类不能再使用,但已经创建的对象不能使用)
- 如果在卸载旧版本后加载新版本,则创建的新对象将属于新版本。但是,在加载新版本之前创建的旧对象不会受到影响,并且仍然是旧版本。
我的问题是,有没有一种简单的方法可以使从旧类版本创建的现有对象“切换”到新版本(但不是新旧版本的合并版本)?在我看来,可能的方法是在卸载/加载后重新创建对象,这不适合插件(不希望它被破坏)。
更新:我的意图是用新版本更新现有对象,而不会出现将旧版本与新版本合并的问题(例如更改参数数量,或删除一个方法)。卸载然后重新加载似乎是最干净的方法,尽管您必须跟踪所有此类对象并在需要时重新创建它们。此外,昂贵的对象可能不适合重新创建。这给我留下了第二个选择,禁止发生意外的合并。只要没有删除任何方法,没有更改方法签名,合并就可以正常工作。
下面是我的测试程序:
$ cat test.rb
load 'v1.rb'
puts "=> 'v1.rb' loaded"
a1 = A.new
puts "=> object a1(#{a1}) created"
a1.common
a1.method_v1
load 'v2.rb'
puts '',"=> class A updated by 'v2.rb'"
a1.common
a1.method_v1
a1.method_v2
a2 = A.new
puts '',"=> object a2(#{a2}) created"
a2.common
a2.method_v1
a2.method_v2
Object.send(:remove_const, 'A')
puts '',"=> class A unloaded"
A.new rescue puts $!
puts '',"=> class A does not exist now"
a1.common
a1.method_v1
a1.method_v2 rescue puts $!
a2.common
a2.method_v1
a2.method_v2
load 'v3.rb'
puts '',"=> 'v3.rb' loaded"
a1.common
a1.method_v1
a1.method_v2 rescue puts $!
a1.method_v3 rescue puts $!
a2.common
a2.method_v1
a2.method_v2
a2.method_v3 rescue puts $!
a3 = A.new
puts '',"=> object a3(#{a3}) create"
a3.common
a3.method_v1 rescue puts $!
a3.method_v2 rescue puts $!
a3.method_v3
样本输出:
$ ruby test.rb
=> 'v1.rb' loaded
=> object a1(#<A:0x1042d4b0>) created
#<A:0x1042d4b0>: common: v1
#<A:0x1042d4b0>: method v1
=> class A updated by 'v2.rb'
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
=> object a2(#<A:0x1042cec0>) created
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
=> class A unloaded
uninitialized constant A
=> class A does not exist now
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
=> 'v3.rb' loaded
#<A:0x1042d4b0>: common: v2
#<A:0x1042d4b0>: method v1
#<A:0x1042d4b0>: method v2
undefined method `method_v3' for #<A:0x1042d4b0>
#<A:0x1042cec0>: common: v2
#<A:0x1042cec0>: method v1
#<A:0x1042cec0>: method v2
undefined method `method_v3' for #<A:0x1042cec0>
=> object a3(#<A:0x1042c3f8>) create
#<A:0x1042c3f8>: common: v3
undefined method `method_v1' for #<A:0x1042c3f8>
undefined method `method_v2' for #<A:0x1042c3f8>
#<A:0x1042c3f8>: method v3
以下是A类的3个版本:
$ cat v1.rb
class A
def common
puts "#{self}: common: v1"
end
def method_v1
puts "#{self}: method v1"
end
end
$ cat v2.rb
class A
def common
puts "#{self}: common: v2"
end
def method_v2
puts "#{self}: method v2"
end
end
$ cat v3.rb
class A
def common
puts "#{self}: common: v3"
end
def method_v3
puts "#{self}: method v3"
end
end
【问题讨论】:
-
您实际上并没有卸载类 A。您将其与其名称断开连接,但类 A 的现有实例仍然持有对该类的引用。该类在可以被垃圾回收时被删除,这发生在该类的所有实例都被垃圾回收时。
标签: ruby metaprogramming