Idiomatic object creation in ruby

前端 未结 6 1953
春和景丽
春和景丽 2020-12-01 14:39

In ruby, I often find myself writing the following:

class Foo
  def initialize(bar, baz)
    @bar = bar
    @baz = baz
  end

  << more stuff >>
         


        
相关标签:
6条回答
  • 2020-12-01 15:18

    I asked a duplicate question, and suggested my own answer there, expecting for a better one, but a satisfactory one did not appear. I will post my own one.

    Define a class method like the following along the spirit of attr_accessor, attr_reader, attr_writer methods.

    class Class
        def attr_constructor *vars
            define_method("initialize") do |*vals|
                vars.zip(vals){|var, val| instance_variable_set("@#{var}", val)}
            end
        end
    end
    

    Then, you can use it like this:

    class Foo
        attr_constructor :foo, :bar, :buz
    end
    
    p Foo.new('a', 'b', 'c')      # => #<Foo:0x93f3e4c @foo="a", @bar="b", @buz="c">
    p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d @foo="a", @bar="b", @buz="c">
    p Foo.new('a', 'b')           # => #<Foo:0x93f3e4e @foo="a", @bar="b", @buz=nil>
    
    0 讨论(0)
  • 2020-12-01 15:19

    One option is that you can inherit your class definition from Struct:

    class Foo < Struct.new(:bar, :baz)
      # << more stuff >>
    end
    
    f = Foo.new("bar value","baz value")
    f.bar #=> "bar value"
    f.baz #=> "baz value"
    
    0 讨论(0)
  • 2020-12-01 15:21

    Struct

    Struct object's are classes which do almost what you want. The only difference is, the initialize method has nil as default value for all it's arguments. You use it like this

    A= Struct.new(:a, :b, :c)
    

    or

    class A < Struc.new(:a, :b, :c)
    end
    

    Struct has one big drawback. You can not inherit from another class.

    Write your own attribute specifier

    You could write your own method to specify attributes

    def attributes(*attr)
      self.class_eval do
        attr.each { |a| attr_accessor a }
        class_variable_set(:@@attributes, attr)
    
          def self.get_attributes
            class_variable_get(:@@attributes)
          end
    
          def initialize(*vars)
            attr= self.class.get_attributes
            raise ArgumentError unless vars.size == attr.size
            attr.each_with_index { |a, ind| send(:"#{a}=", vars[ind]) }
            super()
          end
      end
    end
    
    class A
    end
    
    class B < A
      attributes :a, :b, :c
    end
    

    Now your class can inherit from other classes. The only drawback here is, you can not get the number of arguments for initialize. This is the same for Struct.

    B.method(:initialize).arity # => -1
    
    0 讨论(0)
  • 2020-12-01 15:21

    I sometimes do

    @bar, @baz = bar, baz
    

    Still boilerplate, but it only takes up one line.

    I guess you could also do

    ["bar", "baz"].each do |variable_name|
      instance_variable_set(:"@#{variable_name}", eval(variable_name))
    end
    

    (I'm sure there's a less dangerous way to do that, mind you)

    https://bugs.ruby-lang.org/issues/5825 is a proposal to make the boilerplate less verbose.

    0 讨论(0)
  • 2020-12-01 15:31

    You could use Virtus, I don't think it's the idiomatic way to do so but it does all the boiler plate for you.

    require 'Virtus'
    
    class Foo
      include 'Virtus'
    
      attribute :bar, Object 
      attribute :baz, Object
    
    end
    

    Then you can do things like

    foo = Foo.new(:bar => "bar") 
    foo.bar # => bar
    

    If you don't like to pass an hash to the initializer then add :

    def initialize(bar, baz)
      super(:bar => bar, :baz => baz)
    end
    

    If you don't think it's DRY enough, you can also do

    def initialize(*args)
      super(self.class.attributes.map(&:name).zip(args)])
    end
    
    0 讨论(0)
  • 2020-12-01 15:33

    You could use an object as param.

    class Foo
      attr_accessor :param
    
      def initialize(p)
        @param = p
      end
    end
    
    f = Foo.new
    f.param.bar = 1
    f.param.bax = 2
    

    This does not save much lines in this case but it will if your class has to handle a large number of param. You could also implement a set_param and get_param method if you want to keep your @param var private.

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