代码之家  ›  专栏  ›  技术社区  ›  bryantsai

Ruby中的加载/卸载/更新类

  •  5
  • bryantsai  · 技术社区  · 16 年前

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

    1. 如果加载同一类的新版本而不首先卸载它,则新版本基本上是“top”或“merge”与以前的版本。使用早期版本创建的所有现有对象都将获得类定义“更新”。
    2. 卸载类不会影响用该类创建的现有对象。现有对象保留在刚卸载的任何版本中。(类不能再使用,但不能使用已创建的对象)
    3. 如果在卸载先前版本之后加载新版本,则创建的新对象将是新版本。但是,在加载新版本之前创建的旧对象不会受到影响,仍然是旧版本。

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

    更新 :我的目的是让现有对象使用新版本进行更新,而不会出现将旧版本与新版本合并的问题(如更改参数数量或删除方法)。卸载然后再重新加载似乎是最干净的方法,尽管您必须跟踪所有这些对象,并在需要时重新创建它们。此外,昂贵的物品可能不适合重新创建。这给了我第二个选择,禁止意外的合并发生。只要不删除任何方法,不更改任何方法签名,合并就可以正常工作。

    以下是我的测试程序:

    $ 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
    
    2 回复  |  直到 10 年前
        1
  •  1
  •   Ken Bloom    16 年前

    显然,用新的类定义完全替换类定义是有危险的,无论您是合并新版本还是删除旧版本,并期望对象自动得到更新。这种危险在于对象的旧版本对于新版本可能处于无效状态。(例如,新版本的类在其 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($!)
    

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

    class A < Object
    end
    class A < Object
    end
    class A < String #TypeError: superclass mismatch for class A
    end
    
        2
  •  1
  •   Paweł Gościcki Ywain    10 年前

    简而言之,如果没有严重的黑客攻击,就没有办法做到这一点。我建议你做的是 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)
    

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

    推荐文章