在ruby中加载/卸载/更新类

我做了一些Ruby类动态加载/卸载/更新实验,实现了插件基础设施。 我发现了几点:

  1. 如果在没有首先卸载它的情况下加载同一类的新版本,则新版本基本上与前一版本“顶部”或“合并”。 使用先前版本创建的所有现有对象都将使其类定义“更新”。
  2. 卸载类不会影响使用此类创建的现有对象。 现有的对象可以保留任何卸载的版本。 (类不能再使用,但不能使用已创建的对象)
  3. 如果在卸载先前版本后加载新版本,则创建的新对象将是新版本。 但是,在加载新版本之前创建的旧对象不会受到影响,并且仍然是旧版本。

我的问题是,是否有一种简单的方法可以使现有的对象从旧的类版本“switch”创建到新版本(但不是旧版本和新版本的合并版本)? 在我看来,可能的方法是在卸载/加载后重新创建对象,这不适合插件(不希望它被销毁)。

更新 :我的意图是使用新版本更新现有对象,而不会将旧版本与新版本合并(例如更改参数数量或删除方法)。 卸载然后重新加载似乎是最干净的方法,尽管你必须跟踪所有这些对象并在需要时重新创建它们。 此外,昂贵的物体可能不适合重新创建。 这给我留下了第二个选项,禁止意外合并发生。 只要没有删除方法,没有方法签名改变,合并应该可以正常工作。

以下是我的测试程序:

$ 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(#) created #: common: v1 #: method v1 => class A updated by 'v2.rb' #: common: v2 #: method v1 #: method v2 => object a2(#) created #: common: v2 #: method v1 #: method v2 => class A unloaded uninitialized constant A => class A does not exist now #: common: v2 #: method v1 #: method v2 #: common: v2 #: method v1 #: method v2 => 'v3.rb' loaded #: common: v2 #: method v1 #: method v2 undefined method `method_v3' for # #: common: v2 #: method v1 #: method v2 undefined method `method_v3' for # => object a3(#) create #: common: v3 undefined method `method_v1' for # undefined method `method_v2' for # #: 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 

显然,使用新的类定义替换类定义存在危险,无论是合并新版本还是删除旧版本并期望对象自动更新。 这种危险在于旧版本的对象可能处于新版本的无效状态。 (例如, initialize方法初始化类的新版本可能尚未由旧版本定义的实例变量,但可能会出现比此更微妙的错误)。 无论你如何解决这个问题,都需要小心(以及精心规划的升级路径)。

鉴于你知道你正在升级的版本是什么样的(无论如何你都需要升级),让新版本的类从旧版本的类中删除不需要的方法是很简单的:

 class A remove_method :foo end 

当你说重新定义一个方法来获取不同数量的参数时,我不确定你在谈论什么。 这对我来说可以:

 class A def foo a a end end ainst=A.new p(ainst.foo 1) rescue puts($!) p(ainst.foo 1,2) rescue puts($!) class A def foo a,b [a,b] end end p(ainst.foo 1) rescue puts($!) p(ainst.foo 1,2) rescue puts($!) 

你不能做的唯一事情(AFAIK)就是改变class级的超类。 这是在您第一次定义类时定义的,并且您不允许更改它(尽管您可以再次指定相同的祖先类)。

 class A < Object end class A < Object end class A < String #TypeError: superclass mismatch for class A end 

简而言之,如果没有严重的黑客攻击,就没有办法做到这一点。 我建议你做的是创建一个to_serialized方法,该方法返回一个initialize方法接受的数组以获得相同的状态。 如果您只想复制所有实例变量,可以这样做:

 class A def initialize(instance_variables) instance_variables.each do |key, value| self.instance_variable_set(key, value) end end def to_serialized iv = {} self.instance_variables.each do |key| iv[key] = self.instance_variable_get(key) end end end 

要重新加载方法,您可以这样做:

 obj_state = object.to_serialized Object.send(:remove_const, 'A') load 'file.rb' object = A.new(obj_state) 

请注意,这不会嵌套,因此如果实例变量引用的任何对象也重新加载,则需要自己“序列化”它们。