在Ruby中动态创建类

时间:2009-02-23 07:47:00

标签: 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

我希望能够在运行时定义一组类似于这一类的类(在启动时定义它们一次),其中数组的大小和分配给每个参数的类型是在运行时从外部规范文件定义的。

我有点使用evals工作,但这真的很难看。有更好的方法吗?

3 个答案:

答案 0 :(得分:32)

据我所知,您需要元编程。下面是使用初始化实例变量的初始化方法动态(动态)创建类的代码片段 -

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

希望你能根据自己的要求调整它。

答案 1 :(得分:9)

首先,您的示例代码不适合您的部分原因是您有两个不同的@people变量 - 一个是实例变量,另一个是< em>类实例变量。

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

如果要在类中创建一次变量以在该类的实例方法中使用,请使用constantsclass 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

即便如此,在您的代码的上下文中,您可能不希望所有Family_Type1实例都引用相同的警察和会计师吗?或者你呢?

如果我们切换到使用类变量:

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

运行时初始化可以通过元编程来完成,正如Chirantan所说,但是如果你只是初始化几个类,并且你知道他们的名字是什么,你也可以通过使用从文件中读取的任何内容来完成:< / p>

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

答案 2 :(得分:1)

假设您想在运行时创建每个类型/数组大小不同的类

如果(在Python中)在执行时定义了一个Ruby类(我认为是),那么你可以这样做:

在函数中定义您的类。让函数接收数组大小和类型作为参数,并在其结果中返回类。这样,您就有了一种类工厂来调用spec文件中的每个定义:)

另一方面,如果您想根据实际数据初始化@params,请记住,Ruby是一种动态类型语言:只需将构造函数中的@params重新分配给新数组!

相关问题