使用哈希参数进行 DRY Ruby 初始化 [英] DRY Ruby Initialization with Hash Argument
问题描述
我发现自己在构造函数中使用了很多哈希参数,尤其是在为最终用户将接触到的配置或其他 API 位编写 DSL 时.我最终做的事情如下:
I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. What I end up doing is something like the following:
class Example
PROPERTIES = [:name, :age]
PROPERTIES.each { |p| attr_reader p }
def initialize(args)
PROPERTIES.each do |p|
self.instance_variable_set "@#{p}", args[p] if not args[p].nil?
end
end
end
没有更惯用的方法来实现这一点吗?丢弃的常量和符号到字符串的转换似乎特别令人震惊.
Is there no more idiomatic way to achieve this? The throw-away constant and the symbol to string conversion seem particularly egregious.
推荐答案
你不需要常量,但我认为你不能消除符号到字符串:
You don't need the constant, but I don't think you can eliminate symbol-to-string:
class Example
attr_reader :name, :age
def initialize args
args.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c @name="foo", @age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c @name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil
顺便说一句,你可以看看(如果你还没有)Struct
类生成器类,它有点类似于你正在做的事情,但没有哈希类型初始化(但我想制作足够的生成器类并不难).
BTW, you might take a look (if you haven't already) at the Struct
class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).
试图实现hurikhan的想法,我就是这样来的:
Trying to implement hurikhan's idea, this is what I came to:
module HasProperties
attr_accessor :props
def has_properties *args
@props = args
instance_eval { attr_reader *args }
end
def self.included base
base.extend self
end
def initialize(args)
args.each {|k,v|
instance_variable_set "@#{k}", v if self.class.props.member?(k)
} if args.is_a? Hash
end
end
class Example
include HasProperties
has_properties :foo, :bar
# you'll have to call super if you want custom constructor
def initialize args
super
puts 'init example'
end
end
e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23
由于我对元编程不是那么精通,所以我创建了答案社区 wiki,因此任何人都可以自由更改实现.
As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.
扩展 Marc-Andre 的答案,这是一个通用的、基于 Struct
的方法来创建哈希初始化类:
Expanding on Marc-Andre's answer, here is a generic, Struct
based method to create hash-initialized classes:
class Struct
def self.hash_initialized *params
klass = Class.new(self.new(*params))
klass.class_eval do
define_method(:initialize) do |h|
super(*h.values_at(*params))
end
end
klass
end
end
# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age
# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>
这篇关于使用哈希参数进行 DRY Ruby 初始化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!