Как динамично изменить наследование в 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 определение класса указывает родительский класс, таким образом, они оба наследовались Объекту. Мой первый вопрос: в чем я должен был бы выполнить Agent.hook_up для создания Parent суперкласс Child (так, например, Child объекты могут наследовать метод 'панели').

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

11
задан Grant McLean 27 June 2010 в 10:24
поделиться

4 ответа

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

6
ответ дан 3 December 2019 в 01:51
поделиться

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

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

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

Поскольку родительский элемент является аргументом 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"
23
ответ дан 3 December 2019 в 01:51
поделиться

Только для 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
ответ дан 3 December 2019 в 01:51
поделиться

Как уже указывалось, вам, вероятно, следует изучить модули или динамически создавать классы. Однако вы можете использовать evil-ruby для изменения суперкласса. Доступен даже форк для 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
3
ответ дан 3 December 2019 в 01:51
поделиться