2010-06-27 23 views
9

Me gustaría especificar dinámicamente la clase principal para una clase en Ruby. Considere este código:Cómo alterar dinámicamente la herencia en 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 

Ni el Parent ni la definición de clase Child especifica una clase padre, por lo que ambos heredan del objeto. Mi primera pregunta es: ¿qué tendría que hacer en Agent.hook_up para hacer Parent la superclase de Child (por ejemplo, los objetos Child pueden heredar el método 'bar').

Mi segunda pregunta es: ¿Cómo puedo necesito para pasar el primer argumento de Agent.hook_up, o hay alguna manera el método hook_up puede determinar mediante programación la clase de la cual se llamaba?

+2

Um, si configura dinámicamente o cambia el elemento primario de una clase, entonces es una afirmación razonable de que su modelo de objetos no refleja una verdadera relación * is-a *. Una solución más adecuada es probablemente la composición. – cletus

+1

Debe aclarar si se trata de un ejercicio de reflexión, o por qué querría usar eso en la práctica. Los lenguajes reflectantes dinámicos no significan que esté limpio para cambiar dinámicamente la herencia. Con gran poder (expresivo), viene una gran responsabilidad :) –

+0

@cletus No soy un gran admirador de la herencia en general, pero por el problema que estoy viendo actualmente, la herencia y la relación is-a describen perfectamente la relación. El único problema es que un objeto no sabrá qué es "hasta el momento de la ejecución". –

Respuesta

6

Joshua ya le ha dado una gran lista de alternativas, pero para responder a su pregunta: ¿Puede No cambie la clase superior de una clase después de que la clase se haya creado en ruby. Eso simplemente no es posible.

+6

¡DESAFÍO ACEPTADO! – Borromakot

22

Tal vez usted está buscando para este

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

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

Desde padre es un argumento para Class.new, puede intercambiarlo con otras clases.

He utilizado esta técnica antes al escribir ciertos tipos de pruebas. Pero tengo dificultades para pensar en muchas buenas excusas para hacer tal cosa.


Sospecho que lo que realmente quieres es un módulo.

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" 

Aunque, por supuesto, no hay necesidad de que el agente del todo

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" 
+0

Gracias Joshua. Dado que parece que no puedo cambiar la cadena de herencia en tiempo de ejecución, probablemente terminaré usando alguna variación de su primera opción en mi solución. Aprecio el tiempo que pones para responder. –

+0

Otra cosa que puedes ver, si esto es _realmente_ lo que quieres hacer, es change_class escrito por Ryan Davis. Aquí está el video donde presenta el código a las 5:00 http://rubyconf2008.confreaks.com/evil-code.html Aquí está la página de inicio del proyecto. http://seattlerb.rubyforge.org/change_class/ Tenga en cuenta el descargo de responsabilidad "Sí, es peligroso ... no venga a llorar a mí si su computadora se enciende". –

3

Como se señaló anteriormente, probablemente debería examinar módulos o crear clases dinámicamente. Sin embargo, puede usar evil-ruby para cambiar la superclase. Incluso hay un fork for Ruby 1.9 disponible. Esto solo funciona para MRI. Debería ser fácil de construir en Rubinius (limpiar los cachés sería el tema principal), no hay pistas sobre JRuby. Aquí está el código:

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

Supongo que el uso del "mal" probablemente sería mal visto en el código de producción :-) Crear una clase dinámicamente es probablemente la solución que terminaré usando. ¡Gracias! –

4

Ruby 1.9 solamente: (1.8 es similar, pero el uso RCLASS (auto) -> Super lugar)

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) 
0

de SimpleDelegator clase (en la biblioteca delegate) Rubí puede ayudar , siempre que sea suficiente que el objeto graznar como la clase base, en lugar de realmente sea una instancia de la clase base.

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 
0

Mira esta

class MyClass < inherit_orm("Adapter") 
    end 

Y el selector de clase:

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

lo tanto, cuando MyClass ejemplo que se heredan de una clase dinámica en función de orm y model. Asegúrese de definir ambos en un módulo. Funciona bien en public_activity gem (selector example).

Espero ayudar .. Adiós!

Cuestiones relacionadas