2009-02-23 18 views
9

Mam klasy, który powinien wyglądać tak:Dynamicznie tworzenie klasy w 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 

Chcę być w stanie określić kilka klas podobnych do tego w czasie wykonywania (define je raz przy starcie) gdzie rozmiar tablicy i typ przypisany do każdego parametru jest zdefiniowany w środowisku wykonawczym z zewnętrznego pliku specyfikacji.

W pewnym sensie udało mi się użyć Evalsa, ale to jest naprawdę brzydkie. Lepszy sposób?

Odpowiedz

9

Po pierwsze, część z tego powodu Twój przykładowy kod nie pracuje dla Ciebie jest to, że masz dwa różne @people zmienne - jeden jest instancja zmiennej a drugi jest instancją klasy zmienna.

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 

Jeśli chcesz utworzyć zmiennych jeden raz w klasie do wykorzystania w metodach instancji tej klasy, należy constants lub 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 

Mimo to, w kontekście swojego kodu, to prawdopodobnie nie chcą wszystkie instancje Family_Type1 odnoszą się do tych samych policjantów i Księgowych prawda? A może ty?

Jeśli przełączyć się za pomocą zmiennych Klasa:

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 

Inicjalizacja Runtime można zrobić z metaprogramowanie, jak Chirantan powiedział, ale jeśli inicjowania tylko kilka klas, i wiesz co ich nazwa jest możesz to również zrobić za pomocą tego, co czytasz z pliku:

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

Jest to jedyna odpowiedź, która naprawdę wyjaśnia całą sytuację IMO. – Chuck

+0

niesamowite wyjaśnienie, pomaga w usuwaniu koncepcji ... thnks –

1

Zakładając, że chcemy stworzyć różnych klas na rozmiar/typ tablicy w czasie wykonywania:

Jeśli (jak w Pythonie) klasa Ruby jest określona po wykonaniu (myślę, że jest), to można to zrobić :

Zdefiniuj swoją klasę w funkcji. Niech funkcja otrzyma rozmiar i typ tablicy jako parametry i zwróci klasę w jej wyniku. W ten sposób masz rodzaj fabryki klas, aby wywołać każdą definicję w twoim pliku specyfikacji :)

Jeśli chcesz tylko zainicjować @params na podstawie rzeczywistych danych, pamiętaj, że Rubin jest dynamicznie wpisany język: po prostu przypisz @params w swoim konstruktorze do nowej tablicy!

+0

Dzięki - do tej pory udało się utworzyć wewnątrz funkcji, ale pytanie dotyczy implementacji - np. dodawanie/inicjowanie tablicy poza metodą "initialize" itd. –

+0

Inicjalizuj Chcę tylko wysłać wartość - nie kompletne obiekty. Powiedzmy, że zmienna instancji jest tablicą obiektów pochodnych Person, wtedy chcę, aby tablica z typami była częścią definicji klasy, a podczas inicjowania będę przekazywać tylko nazwy każdej osoby. –

32

Z tego co rozumiem, potrzebujesz meta-programowania. Oto fragment kodu do tworzenia klas dynamicznie (w locie) z metody initialize który inicjuje wystąpienie 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 

Spodziewać się można dostosować go do własnych potrzeb.

+0

Dzięki, wydaje się trudniejsze, gdy zmienną instancji jest tablica obiektów, a metoda initialize powinna ustawiać tylko określone pole w każdym obiekcie. –

+0

Dude, w twoim przypadku możesz zrobić instance_variable_set ("@ params [# {i}]. Age", wiek [i]) i gotowe ... To jak radzić sobie z tablicami. Musisz trochę w to zagłębić, żeby to zrozumieć. – Chirantan