【问题标题】:How to dynamically alter inheritance in Ruby如何在 Ruby 中动态改变继承
【发布时间】:2010-06-27 10:24:18
【问题描述】:

我想在 Ruby 中动态指定一个类的父类。考虑这段代码:

class Agent
  def self.hook_up(calling_class, desired_parent_class)
    # Do some magic here
  end
end

class Parent
  def bar
    puts "bar"
  end
end

class Child
  def foo
    puts "foo"
  end

  Agent.hook_up(self, Parent)
end

Child.new.bar

ParentChild 类定义都没有指定父类,因此它们都继承自 Object。我的第一个问题是:我需要在Agent.hook_up 中做什么才能使Parent 成为Child 的超类(例如Child 对象可以继承'bar' 方法)。

我的第二个问题是:我是否需要将第一个参数传递给Agent.hook_up,或者hook_up 方法是否可以通过某种方式以编程方式确定调用它的类?

【问题讨论】:

  • 嗯,如果您正在动态设置或更改类的父类,那么可以合理地断言您的对象模型没有反映真正的 is-a 关系。一个更合适的解决方案可能是组合。
  • 你应该清楚这是否是一个思考练习,或者你为什么要在实践中使用它。动态反射语言并不意味着动态改变继承是干净的。拥有强大(表现力)的力量,就伴随着巨大的责任:)
  • @cletus 一般来说,我不是继承的忠实拥护者,但对于我目前正在研究的问题,继承和 is-a 关系完美地描述了这种关系。唯一的问题是对象直到运行时才知道它是什么。

标签: ruby metaprogramming


【解决方案1】:

也许你正在寻找这个

Child = Class.new Parent do
  def foo
    "foo"
  end
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"

由于 parent 是 Class.new 的参数,因此您可以将其与其他类交换。

我以前在编写某些类型的测试时使用过这种技术。但是我很难想出很多好的借口来做这样的事情。


我怀疑你真正想要的是一个模块。

class Agent
  def self.hook_up(calling_class, desired_parent_class)
    calling_class.send :include , desired_parent_class
  end
end

module Parent
  def bar
    "bar"
  end
end

class Child
  def foo
    "foo"
  end

  Agent.hook_up(self, Parent)
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"

当然,根本不需要代理

module Parent
  def bar
    "bar"
  end
end

class Child
  def foo
    "foo"
  end

  include Parent
end

Child.ancestors   # => [Child, Parent, Object, Kernel]
Child.new.bar     # => "bar"
Child.new.foo     # => "foo"

【讨论】:

  • 谢谢约书亚。由于我似乎无法在运行时更改继承链,因此我可能最终会在我的解决方案中使用您的第一个选项的一些变体。感谢您花时间回复。
  • 如果这真的是你想要做的,你可以看看另一件事,那就是 Ryan Davis 编写的 change_class。这是他在5:00 rubyconf2008.confreaks.com/evil-code.html介绍代码的视频这里是项目的主页。 seattlerb.rubyforge.org/change_class 请注意免责声明“是的,这很危险……如果你的电脑着火了,不要来找我哭。”
【解决方案2】:

Joshua 已经为您提供了很多替代方案,但要回答您的问题:在 ruby​​ 中创建类之后,您无法更改类的超类。这根本不可能。

【讨论】:

  • @Borromakot 挑战状态?
  • @Borromakot,除非你覆盖他们的记忆位置:)
【解决方案3】:

仅限 Ruby 1.9:(1.8 类似,但使用 RCLASS(self)->super 代替)

require 'inline'
class Class
    inline do |builder|

        builder.c %{            
            VALUE set_super(VALUE sup) {
                RCLASS(self)->ptr->super = sup;
                return sup;
            }
        }

        builder.c %{
            VALUE get_super() {
                return RCLASS(self)->ptr->super;
            }
        }

    end


J = Class.new
J.set_super(Class.new)

【讨论】:

    【解决方案4】:

    正如已经指出的,您可能应该研究模块或动态创建类。但是,您可以使用evil-ruby 来更改超类。甚至还有一个fork for Ruby 1.9 可用。这仅适用于 MRI。应该很容易在 Rubinius 上构建(清除方法缓存将是主要问题),不知道 JRuby。代码如下:

    require 'evil'
    
    class Agent
      def self.hook_up(calling_class, desired_parent_class)
        calling_class.superclass = desired_parent_class
      end
    end
    
    class Parent
      def bar
        puts "bar"
      end
    end
    
    class Child
      def foo
        puts "foo"
      end
    
      Agent.hook_up(self, Parent)
    end
    
    Child.new.bar
    

    【讨论】:

    • 我猜想在生产代码中使用“邪恶”可能会令人不悦:-) 动态创建类可能是我最终会使用的解决方案。谢谢!
    【解决方案5】:

    Ruby 的SimpleDelegator 类(在delegate 库中)可能会有所帮助,前提是对象类似于 基类,而不是实际上是 基类。

    require 'delegate'
    
    class Agent < SimpleDelegator
      def baz
        puts "baz"
      end
    end
    
    class BarParent
      def bar
        puts "bar"
      end
    end
    
    class FooParent
      def foo
        puts "foo"
      end
    end
    
    agent = Agent.new(FooParent.new)
    agent.baz    # => baz
    agent.foo    # => foo
    agent.__setobj__(BarParent.new)
    agent.baz    # => baz
    agent.bar    # => bar
    

    【讨论】:

      【解决方案6】:

      看看这个

        class MyClass < inherit_orm("Adapter")
        end
      

      还有类选择器:

        def inherit_orm(model="Activity", orm=nil)
          orm = Config.orm || orm
          require "orm/#{orm.to_s}"
          "ORM::#{orm.to_s.classify}::#{model}".constantize
        end
      

      因此,当实例 MyClass 时,它将继承自依赖于 ormmodel 的动态类。 一定要在一个模块中定义两者。它在public_activity gem (selector example) 中运行良好。

      希望能帮到你。。再见!

      【讨论】:

        【解决方案7】:

        我知道这个问题已经很老了,并且已经有了一些很好的答案。但是我仍然错过了某个解决方案。

        如果您的意图不是动态分配超类,而是创建一个挂钩来执行一些继承代码 (XY Problem)。有一种内置方法可以做到这一点。

        继承(子类)

        每当创建当前类的子类时调用回调。

        例子:

        class Foo
          def self.inherited(subclass)
            puts "New subclass: #{subclass}"
          end
        end
        
        class Bar < Foo
        end
        
        class Baz < Bar
        end
        

        产生:

        New subclass: Bar
        New subclass: Baz
        

        见:Class#inherited

        如果您打算动态创建类,我建议您查看the answer of Joshua Cheek

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-03-10
          • 1970-01-01
          • 2013-06-24
          • 2023-02-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多