我会采用以下方法:
module StructVivificator
def self.prepended(base)
base.send(:define_method, :default_proc) do |&λ|
instance_variable_set(:@λ, λ)
end
end
def [](name)
super || @λ && @λ.() # or more sophisticated checks
end
end
Foo = Struct.new(:bar) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc { 42 } # declare a `default_proc` as in Hash
foo[:bar] += 1 # => 43
foo.bar += 1 # => 44
foo.bar
foo[:bar]
上面通过魔术在后台调用method_missing
,所以唯一要覆盖的是Struct#[]
方法。
预先添加一个模块使其更健壮、更适合每个实例并且通常更灵活。
上面的代码只是一个例子。要复制Hash#default_proc
一个可能的行为(感谢@Stefan 的评论):
module StructVivificator
def self.prepended(base)
raise 'Sorry, structs only!' unless base < Struct
base.singleton_class.prepend(Module.new do
def new(*args, &λ) # override `new` to accept block
super(*args).tap { @λ = λ }
end
end)
base.send(:define_method, :default_proc=) { |λ| @λ = λ }
base.send(:define_method, :default_proc) { |&λ| λ ? @λ = λ : @λ }
# override accessors (additional advantage: performance/clarity)
base.members.each do |m|
base.send(:define_method, m) { self[m] }
base.send(:define_method, "#{m}=") { |value| self[m] = value }
end
end
def [](name)
super || default_proc && default_proc.(name) # or more sophisticated checks
end
end
现在default_proc
lambda 将收到一个name
来决定在这种情况下如何表现。
Foo = Struct.new(:bar, :baz) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc = ->(name) { name == :bar ? 42 : 0 }
puts foo.bar # => 42
puts foo[:bar] += 1 # => 43
puts foo.bar += 1 # => 44
puts foo[:baz] += 1 # => 1