2009-02-23 14 views
9

tengo una clase que debe ser algo como esto:la creación dinámica de clase en Ruby

class Family_Type1 
    @people = Array.new(3) 
    @people[0] = Policeman.new('Peter', 0) 
    @people[1] = Accountant.new('Paul', 0) 
    @people[2] = Policeman.new('Mary', 0) 

    def initialize(*ages) 
     for i in 0 ... @people.length 
      @people[i].age = ages[i] 
     end 
    end 
end 

Quiero ser capaz de definir un montón de clases similares a éste en tiempo de ejecución (definirlos una vez en el arranque) donde el tamaño de la matriz y el tipo asignado a cada parámetro se define en tiempo de ejecución a partir de un archivo de especificación externo.

De alguna manera lo hice funcionar usando evals pero esto es realmente feo. ¿Alguna mejor manera?

Respuesta

9

En primer lugar, parte de la razón de su código de ejemplo no funciona para usted es que usted tiene dos variables de @people diferentes - uno es una instancia de variable y la otra es una instancia de la clase variable de .

class Example 
    # we're in the context of the Example class, so 
    # instance variables used here belong to the actual class object, 
    # not instances of that class 
    self.class #=> Class 
    self == Example #=> true 
    @iv = "I'm a class instance variable" 

    def initialize 
    # within instance methods, we're in the context 
    # of an _instance_ of the Example class, so 
    # instance variables used here belong to that instance. 
    self.class #=> Example 
    self == Example #=> false 
    @iv = "I'm an instance variable" 
    end 
    def iv 
    # another instance method uses the context of the instance 
    @iv #=> "I'm an instance variable" 
    end 
    def self.iv 
    # a class method, uses the context of the class 
    @iv #=> "I'm a class instance variable" 
    end 
end 

Si desea crear variables de una vez en una clase para su uso en los métodos de instancia de esa clase, utilizar constants o class variables.

class Example 
    # ruby constants start with a capital letter. Ruby prints warnings if you 
    # try to assign a different object to an already-defined constant 
    CONSTANT_VARIABLE = "i'm a constant" 
    # though it's legit to modify the current object 
    CONSTANT_VARIABLE.capitalize! 
    CONSTANT_VARIABLE #=> "I'm a constant" 

    # class variables start with a @@ 
    @@class_variable = "I'm a class variable" 

    def c_and_c 
    [ @@class_variable, CONSTANT_VARIABLE ] #=> [ "I'm a class variable", "I'm a constant" ] 
    end 
end 

A pesar de ello, en el contexto de su código, es probable que no quieren que todas las instancias de Family_Type1 que se refieren a los mismos policías y Contadores correctas? ¿O usted?

Si cambiamos a la utilización de variables de clase:

class Family_Type1 
    # since we're initializing @@people one time, that means 
    # all the Family_Type1 objects will share the same people 
    @@people = [ Policeman.new('Peter', 0), Accountant.new('Paul', 0), Policeman.new('Mary', 0) ] 

    def initialize(*ages) 
     @@people.zip(ages).each { |person, age| person.age = age } 
    end 
    # just an accessor method 
    def [](person_index) 
     @@people[person_index] 
    end 
end 
fam = Family_Type1.new(12, 13, 14) 
fam[0].age == 12 #=> true 
# this can lead to unexpected side-effects 
fam2 = Family_Type1.new(31, 32, 29) 
fam[0].age == 12 #=> false 
fam2[0].age == 31 #=> true 
fam[0].age == 31 #=> true 

La inicialización en tiempo de ejecución se puede hacer con metaprogramming, como dijo Chirantan, pero si sólo se está inicializando algunas clases, y usted sabe lo que su nombre es, También puede hacerlo utilizando cualquier cosa que lea en el archivo:

PARAMS = File.read('params.csv').split("\n").map { |line| line.split(',') } 
make_people = proc do |klasses, params| 
    klasses.zip(params).map { |klass,name| klass.new(name, 0) } 
end 
class Example0 
    @@people = make_people([ Fireman, Accountant, Fireman ], PARAMS[0]) 
end 
class Example1 
    @@people = make_people([ Butcher, Baker, Candlestickmaker ], PARAMS[0]) 
end 
+0

Esta es la única respuesta que realmente explica la situación completa de la OMI. – Chuck

+0

explicación impresionante, ayuda a borrar conceptos ... gracias –

1

Suponiendo que desea crear clases diferentes por tamaño de letra/matriz en tiempo de ejecución:

Si (como en Python) una clase de Ruby se define cuando se ejecuta (creo que es), entonces usted puede hacer esto :

Defina su clase dentro de una función. Haga que la función reciba el tamaño de matriz y escriba como parámetros y devuelva la clase en su resultado. De esta manera, tiene una especie de fábrica de clases para llamar para cada definición en su archivo de especificaciones :)

Si, por el contrario, desea simplemente inicializar @params basándose en datos reales, tenga en cuenta que Ruby es una dinámica lenguaje escrito: ¡simplemente reasigne @params en su constructor a la nueva matriz!

+0

Gracias - lo he conseguido hasta ahora con la creación dentro de una función, pero la pregunta es sobre la implementación - por ejemplo agregando/inicializando la matriz fuera del método de "inicialización", etc. –

+0

En inicializar, solo deseo enviar el valor, no los objetos completos. Digamos que la variable de instancia es una matriz de objetos derivados de personas, luego quiero que la matriz con los tipos sea parte de la definición de clase, y en inicializar solo pasaré los nombres de cada persona. –

32

Por lo que entiendo, necesita meta-programación. Aquí es un fragmento de código para la creación de clases de forma dinámica (sobre la marcha) con el método initialize que inicializa ejemplo variables-

class_name = 'foo'.capitalize 
klass = Object.const_set(class_name,Class.new) 

names = ['instance1', 'instance2'] # Array of instance vars 

klass.class_eval do 
    attr_accessor *names 

    define_method(:initialize) do |*values| 
    names.each_with_index do |name,i| 
     instance_variable_set("@"+name, values[i]) 
    end 
    end 
    # more... 
end 

espera que usted pueda ajustar para que se adapte a sus necesidades.

+0

Gracias, parece más complicado cuando la variable de instancia es una matriz de objetos, y el método de inicialización solo debe establecer un campo determinado en cada objeto. –

+0

Amigo, en tu caso puedes hacer instance_variable_set ("@ params [# {i}]. Age", ages [i]) y ya terminaste ... Así es como puedes manejar matrices. Necesitas profundizar un poco para entenderlo. – Chirantan

Cuestiones relacionadas