Как динамически изменять наследование в Ruby

Я хотел бы динамически указать родительский класс для класса в 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

Ни определение класса Parent, ни Child не указывает родительский класс, поэтому они оба наследуются от Object. Мой первый вопрос: что мне нужно сделать в Agent.hook_up, чтобы сделать Parent суперкласс из Child (так, например, Child объекты могут наследовать метод "bar" ).

Мой второй вопрос: мне нужно передать первый аргумент в Agent.hook_up, или есть способ, которым метод hook_up может программным образом определить класс, из которого он был вызван?

Ответ 1

Джошуа уже дал вам отличный список альтернатив, но, чтобы ответить на ваш вопрос: вы не можете изменить суперкласс класса после того, как класс был создан в рубине. Это просто невозможно.

Ответ 2

Возможно, вы ищете это

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"

Ответ 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 для Ruby 1.9. Это работает только для МРТ. Должна быть легко построена на 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 class (в delegate) может помочь при условии, что достаточно, чтобы объект quack, как базовый класс, а не был фактически экземпляром базового класса.

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 он будет наследовать от динамического класса в зависимости от orm и model. Обязательно определите как в модуле. Он отлично работает в public_activity gem (пример селектора).

Я надеюсь помочь.. Пока!

Ответ 7

Я знаю, что этот вопрос довольно старый и уже имеет несколько хороших ответов. Однако я все еще скучаю по определенному решению.

Если вы намерены не динамически назначать суперкласс, а создать ловушку для выполнения некоторого кода при наследовании (проблема XY). Существует встроенный способ сделать это.

унаследовал (подкласс)

Обратный вызов вызывается всякий раз, когда создается подкласс текущего класса.

Пример:

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

Если вы хотите динамически создавать классы, я бы порекомендовал посмотреть на ответ Джошуа Чика.