在ruby中创建惯用对象

在ruby中,我经常发现自己编写以下内容:

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

甚至

 class Foo attr_accessor :bar, :baz def initialize(bar, baz) @bar = bar @baz = baz end <> end 

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

一个选项是您可以从Structinheritance您的类定义:

 class Foo < Struct.new(:bar, :baz) # << more stuff >> end f = Foo.new("bar value","baz value") f.bar #=> "bar value" f.baz #=> "baz value" 

我问了一个重复的问题 ,并在那里提出了我自己的答案,希望有一个更好的答案,但是没有出现令人满意的问题。 我会张贴自己的。

根据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') # => # p Foo.new('a', 'b', 'c', 'd') # => # p Foo.new('a', 'b') # => # 

结构

Struct对象是几乎可以满足您需求的类。 唯一的区别是,initialize方法的nil作为所有参数的默认值。 你这样使用它

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

要么

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

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

编写自己的属性说明符

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

 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 

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

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

您可以使用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 

我有时会这样做

 @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是一个使样板文件更简洁的提案。

您可以使用对象作为参数。

 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方法。