What is the most efficient way to initialize a Class in Ruby with different parameters and default values?

后端 未结 7 1278
遇见更好的自我
遇见更好的自我 2020-12-23 11:59

I would like to have a class and some attributes which you can either set during initialization or use its default value.

class Fruit
  attr_accessor :color,         


        
相关标签:
7条回答
  • 2020-12-23 12:25

    Since Ruby 2.0 there is support of named or keyword parameters.

    You may use:

    class Fruit
      attr_reader      :color, :type
    
      def initialize(color: 'green', type: 'pear')
        @color = color
        @type = type
      end
    
      def to_s
        "#{color} #{type}"
      end
    end
    
    puts(Fruit.new)                                    # prints: green pear
    puts(Fruit.new(:color => 'red', :type => 'grape')) # prints: red grape
    puts(Fruit.new(:type => 'pomegranate')) # prints: green pomegranate
    

    Some interesting notes on this topic:

    • Keyword arguments in Ruby 2.0
    • Ruby 2.0.0 by Example
    • Named parameters in Ruby 2
    0 讨论(0)
  • 2020-12-23 12:25

    I'd do it like this:

    class Fruit
      attr_accessor :color, :type
    
      def initialize(args={})
        options = {:color => 'green', :type => 'pear'}.merge(args)
    
        self.color = options[:color]
        self.type  = options[:type]
      end
    end
    
    apple = Fruit.new(:color => 'red', :type => 'apple')
    

    This way, you never have to worry about missing arguments--or their order--and you'll always have your default values right there. .merge will of course overwrite the default values if they're present.

    0 讨论(0)
  • 2020-12-23 12:26

    I like vonconrad's answer but would have a separate defaults method. Maybe it's not efficient in terms of lines of code, but it's more intention-revealing and involves less cognitive overhead, and less cognitive overhead means more efficient dev onboarding.

    class Fruit
      attr_accessor :color, :type
    
      def initialize(args={})
        options = defaults.merge(args)
    
        @color = options.fetch(:color)
        @type  = options.fetch(:type)
      end
    
      def defaults
        {
          color: 'green',
          type:  'pear'
        }
      end
    end
    
    apple = Fruit.new(:color => 'red', :type => 'apple')
    
    0 讨论(0)
  • 2020-12-23 12:36

    More simple way:

    class Fruit
      attr_accessor :color, :type
      def initialize(color = 'green', type = 'pear')
        @color = color
        @type = type
      end
      def to_s
        "#{color} #{type}"
      end
    end
    
    
    puts Fruit.new # prints: green pear
    puts Fruit.new('red','apple') # prints: red apple
    puts Fruit.new(nil,'pomegranate') # prints: green pomegranate
    
    0 讨论(0)
  • 2020-12-23 12:37

    The typical way to solve this problem is with a hash that has a default value. Ruby has a nice syntax for passing hash values, if the hash is the last parameter to a method.

    class Fruit
      attr_accessor :color, :type
    
      def initialize(params = {})
        @color = params.fetch(:color, 'green')
        @type = params.fetch(:type, 'pear')
      end
    
      def to_s
        "#{color} #{type}"
      end
    end
    
    puts(Fruit.new)                                    # prints: green pear
    puts(Fruit.new(:color => 'red', :type => 'grape')) # prints: red grape
    puts(Fruit.new(:type => 'pomegranate')) # prints: green pomegranate
    

    A good overview is here: http://deepfall.blogspot.com/2008/08/named-parameters-in-ruby.html

    0 讨论(0)
  • 2020-12-23 12:46

    Brian's answer is excellent but I would like to suggest some modifications to make it mostly meta:

    class Fruit
    
      # Now this is the only thing you have to touch when adding defaults or properties
      def set_defaults
        @color ||= 'green'
        @type  ||= 'pear'
      end
    
      def initialize(params = {})
        params.each { |key,value| instance_variable_set("@#{key}", value) }
        set_defaults
        instance_variables.each {|var| self.class.send(:attr_accessor, var.to_s.delete('@'))}
      end
    
      def to_s
        instance_variables.inject("") {|vars, var| vars += "#{var}: #{instance_variable_get(var)}; "}
      end
    
    end
    
    puts Fruit.new
    puts Fruit.new :color => 'red', :type => 'grape'  
    puts Fruit.new :type => 'pomegranate'
    puts Fruit.new :cost => 20.21
    puts Fruit.new :foo => "bar"
    
    
    f = Fruit.new :potato => "salad"
    puts "f.cost.nil? #{f.cost.nil?}"
    

    Which outputs:

    @color: green; @type: pear; 
    @color: red; @type: grape; 
    @color: green; @type: pomegranate; 
    @color: green; @type: pear; @cost: 20.21; 
    @color: green; @type: pear; @foo: bar; 
    f.cost.nil? true
    

    Of course this wouldn't be a perfect solution for everything but it gives you some ideas on making your code more dynamic.

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