Как динамически изменить наследование в Ruby - PullRequest
10 голосов
/ 27 июня 2010

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

Ответы [ 7 ]

23 голосов
/ 27 июня 2010

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

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"
6 голосов
/ 27 июня 2010

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

4 голосов
/ 28 июня 2010

Только 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)
3 голосов
/ 27 июня 2010

Как уже отмечалось, вам, вероятно, следует изучить модули или динамически создавать классы.Однако вы можете использовать evil-ruby , чтобы изменить суперкласс.Есть даже вилка для Ruby 1.9 .Это работает только для МРТ.Должно быть легко построить на Рубиниусе (основной проблемой будет очистка кеша методов), без понятия о 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
0 голосов
/ 04 февраля 2019

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

Если вы хотите не динамически назначать суперкласс, а создать ловушку для выполнения некоторого кода при наследовании ( 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

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

0 голосов
/ 28 июля 2015

Посмотрите на это

  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 ( пример селектора ).

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

0 голосов
/ 29 мая 2015

Ruby's SimpleDelegator класс (в библиотеке делегат ) может помочь, при условии, что достаточно, чтобы объект крякал как базовый класс, а не фактически 1007 * быть экземпляром базового класса.

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
...