2010-06-27 2 views
9

Vorrei specificare in modo dinamico la classe genitore per una classe in Ruby. Considerate questo codice:Come modificare dinamicamente l'ereditarietà in 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 

Né il Parent né la definizione di classe Child specifica una classe genitore, in modo che entrambi ereditano da Object. La mia prima domanda è: cosa dovrei fare in Agent.hook_up per rendere Parent la superclasse di Child (così ad esempio gli oggetti Child possono ereditare il metodo "bar").

mia seconda domanda è: posso bisogno di passare il primo argomento di Agent.hook_up, o c'è qualche modo il metodo hook_up può programmazione determinare la classe da cui è stato chiamato?

+2

Um, se si sta impostando o cambiando dinamicamente il genitore di una classe, allora è ragionevole affermare che il proprio modello di oggetto non riflette una vera relazione * is-a *. Una soluzione più adatta è probabilmente la composizione. – cletus

+1

È necessario chiarire se si tratta di un esercizio di pensiero o perché si vorrebbe usarlo in pratica. I linguaggi riflettenti dinamici non significano che è pulito per cambiare dinamicamente l'ereditarietà. Con una grande potenza (espressiva), derivano grandi responsabilità :) –

+0

@letletus Non sono un grande fan dell'ereditarietà in generale, ma per il problema che sto esaminando, l'ereditarietà e l'is-a, la relazione descrive perfettamente la relazione. L'unico problema è che un oggetto non saprà cosa è "fino a ora di esecuzione". –

risposta

6

Joshua ha già dato una grande lista di alternative, ma per rispondere alla tua domanda: è possibile cambiare la superclasse di una classe dopo che la classe è stata creata in ruby. Questo semplicemente non è possibile.

+6

CHALLENGE ACCETTATO! – Borromakot

22

Forse siete alla ricerca di questo

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

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

Dal genitore è un argomento per Class.new, è possibile scambiare fuori con le altre classi.

Ho usato questa tecnica prima durante la scrittura di determinati tipi di test. Ma ho difficoltà a pensare a molte buone scuse per fare una cosa del genere.


Sospetto che quello che vuoi sia un modulo.

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" 

Anche se, naturalmente, non è necessario per l'agente a tutti

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

Grazie Joshua. Poiché sembra che non possa modificare la catena di ereditarietà in fase di esecuzione, probabilmente finirò per utilizzare alcune varianti della prima opzione nella mia soluzione. Apprezzo il tempo che hai dedicato a rispondere. –

+0

Un'altra cosa che puoi guardare, se questo è _really_ quello che vuoi fare, è change_class scritto da Ryan Davis. Ecco il video in cui introduce il codice alle 05:00 http://rubyconf2008.confreaks.com/evil-code.html Ecco la home page del progetto. http://seattlerb.rubyforge.org/change_class/ Nota la dichiarazione di non responsabilità "Sì, è pericoloso ... non venire a piangere da me se il computer si accende". –

3

Come già sottolineato, probabilmente si dovrebbe esaminare i moduli o creare dinamicamente le classi. Tuttavia, è possibile utilizzare evil-ruby per modificare la superclasse. C'è anche un fork for Ruby 1.9 disponibile. Questo funziona solo per la risonanza magnetica. Dovrebbe essere facile da costruire su Rubinius (i metodi di cancellazione dei cache sarebbero il problema principale), nessun indizio su JRuby. Ecco il codice:

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

Immagino che l'uso del "male" sarebbe probabilmente disapprovato nel codice di produzione :-) Creare dinamicamente una classe è probabilmente la soluzione che finirò usando. Grazie! –

4

Ruby 1.9 solo: (1,8 è simile, ma utilizzare RCLASS (auto) -> Super posto)

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

di SimpleDelegator di classe (nella biblioteca delegate) Rubino può aiutare , a condizione che sia sufficiente che l'oggetto cicalino come la classe base, piuttosto che effettivamente sia un'istanza di la classe 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

Guardate questa

class MyClass < inherit_orm("Adapter") 
    end 

E il selettore di classe:

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

Così, quando esempio MyClass sarà ereditare da una classe dinamica a seconda delle orm e model. Assicurati di definire entrambi in un modulo. Funziona bene nella gemma public_activity (selector example).

Spero di aiutare .. Ciao!