在ruby中创建惯用对象

时间:2009-11-22 13:05:05

标签: ruby

在红宝石中,我经常发现自己写了以下内容:

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

  << more stuff >>

end

甚至

class Foo
  attr_accessor :bar, :baz

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

  << more stuff >>

end

我总是希望尽可能地减少样板 - 所以有没有更惯用的方法在ruby中创建对象?

6 个答案:

答案 0 :(得分:11)

一个选项是您可以从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"

答案 1 :(得分:10)

我问a duplicate question,并在那里建议我自己的答案,期待更好的答案,但是没有出现令人满意的答案。我会张贴自己的。

根据attr_accessorattr_readerattr_writer方法的精神定义类似以下的类方法。

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

然后,您可以像这样使用它:

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>

答案 2 :(得分:2)

STRUCT

Struct对象是几乎可以完成你想要的类。唯一的区别是,initialize方法的nil作为所有参数的默认值。你像这样使用它

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

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

Struct有一个很大的缺点。你不能从另一个类继承。

编写您自己的属性说明符

您可以编写自己的方法来指定属性

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

现在你的类可以从其他类继承。这里唯一的缺点是,你无法获得初始化的参数数量。这与Struct相同。

B.method(:initialize).arity # => -1

答案 3 :(得分:2)

您可以使用Virtus,我认为这不是惯用的方法,但它会为您完成所有锅炉板。

require 'Virtus'

class Foo
  include 'Virtus'

  attribute :bar, Object 
  attribute :baz, Object

end

然后你可以做像

这样的事情
foo = Foo.new(:bar => "bar") 
foo.bar # => bar

如果您不想将哈希传递给初始化程序,请添加:

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

如果你认为它不够干,你也可以

def initialize(*args)
  super(self.class.attributes.map(&:name).zip(args)])
end

答案 4 :(得分:1)

我有时会这样做

@bar, @baz = bar, baz

仍然是样板,但它只占用一行。

我想你也可以做

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

(我相信这样做的危险性较小,请注意)

https://bugs.ruby-lang.org/issues/5825是一个使样板文件更简洁的提案。

答案 5 :(得分:-1)

您可以将对象用作参数。

class Foo
  attr_accessor :param

  def initialize(p)
    @param = p
  end
end

f = Foo.new
f.param.bar = 1
f.param.bax = 2

在这种情况下,这不会节省太多行,但是如果你的类必须处理大量的参数,那么它就会存在。如果要将@param var保密,也可以实现set_param和get_param方法。