Dynamically creating class in Ruby

后端 未结 3 2054
后悔当初
后悔当初 2020-12-29 12:43

I have a class that should look something like this:

class Family_Type1
    @people = Array.new(3)
    @people[0] = Policeman.new(\'Peter\', 0)
    @people[1         


        
相关标签:
3条回答
  • 2020-12-29 13:03

    First off, part of the reason your example code isn't working for you is that you have two different @people variables - one is an instance variable and the other is a class instance variable.

    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
    

    If you want to create variables one time in a class to use in instance methods of that class, use constants or 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
    

    Even so, in the context of your code, you probably don't want all your instances of Family_Type1 to refer to the same Policemen and Accountants right? Or do you?

    If we switch to using class variables:

    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
    

    The runtime initialization can be done with metaprogramming, as Chirantan said, but if you are only initializing a few classes, and you know what their name is, you can also do it just by using whatever you read from the file:

    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 讨论(0)
  • 2020-12-29 13:15

    Assuming you want to create different classes per type/array size at runtime:

    If (like in Python) a Ruby class is defined when executed (I think it is), then you can do this:

    Define your class inside a function. Have the function recieve array size and type as parameters and return the class in its result. That way, you have a sort of class factory to call for each definition in your spec file :)

    If on the other hand you want to just initialize @params based on actual data, keep in mind, that Ruby is a dynamically typed language: Just reassign @params in your constructor to the new array!

    0 讨论(0)
  • 2020-12-29 13:19

    From what I understand, you need meta-programming. Here is a snippet of code for creating classes dynamically (on the fly) with initialize method that initializes instance 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
    

    Hope you can tweak it to suit your requirements.

    0 讨论(0)
提交回复
热议问题