有一种模式我发现自己经常使用,所以我想把它弄干。我有这样的东西:
class InfoGatherer
def foo
true
end
def people
unless @people
@people = # Long and complex calculation (using foo)
end
@people
end
end
我想干这个看起来像这样:
class InfoGatherer
extend AttrCalculator
def foo
true
end
attr_calculator(:people) { # Long and complex calculation (using foo) }
end
为实现这一目标,我将模块AttrCalculator
定义为InfoGatherer
。这就是我的尝试:
module AttrCalculator
def attr_calculator(variable_name_symbol)
variable_name = "@#{variable_name_symbol}"
define_method variable_name_symbol do
unless instance_variable_defined?(variable_name)
instance_variable_set(variable_name, block.call)
end
instance_variable_get(variable_name)
end
end
end
不幸的是,当我尝试像InfoGatherer.new.people
这样简单的事情时,我得到了:
NameError: undefined local variable or method `foo' for InfoGatherer:Class
嗯,这很奇怪。为什么block
在InfoGatherer:Class
范围内运行,而不是在其InfoGatherer.new
实例中运行?
我知道我无法使用yield
,因为这会尝试捕获错误的区块,如here所示。
我尝试在上面self.instance_exec(block)
的位置使用block.call
,但之后又收到了一个新错误:
LocalJumpError: no block given
咦?我在this SO question中看到了同样的错误,但我已经使用了括号表示法,所以答案似乎并不适用。
我也尝试使用class_eval
,但我不确定如何在字符串中调用block
。这肯定不起作用:
class_eval("
def #{variable_name_symbol}
unless #{variable_name}
#{variable_name} = #{block.call}
end
#{variable_name}
end
")
答案 0 :(得分:4)
该用例称为memoization。它可以很容易地完成:
def people
@people ||= # Long and complex calculation (using foo)
end
你不应该像你一样陷入困境。
答案 1 :(得分:1)
扩展最后的人
def people(varariable = nil)
@people ||= ComplexCalculation.new(variable).evaluate
end
class ComplexCalculation
def initialize(variable)
@variable = variable
end
def evaluate(variable)
#stuff
end
end
通过提取此类,您可以隔离这种复杂性并获得更好的体验。
答案 2 :(得分:1)
问题是,在define_method
内,self
出人意料InfoGatherer
,而不是InfoGatherer
的实例。所以我和self.instance_exec(block)
走在了正确的轨道上。
工作解决方案是self.instance_exec(&block)
(请注意&符号)。我想解释器并不认识block
是一个块,除非你将它标记为?如果有人能比我更好地解释这一点,请做。
作为旁注,这是不是解决此特定问题的最佳方法。请参阅@ sawa的答案,了解记忆复杂计算的简洁方法。