Вы можете сделать это на шаг дальше, чем другие ответы, если хотите. Вместо того, чтобы изменять код, чтобы пересчитать только при необходимости, вы можете указать код, который изменяет код. Все любят метапрограммирование.
Вот код, который берет имя метода, который выполняет потенциально длительный расчет, и список имен методов, которые при вызове invalidate любой предыдущий расчет и записывают код, чтобы обернуть методы и выполнить только вычисление if необходимо, возвращая сохраненное значение, если нет.
module ExpensiveCalculation
def recalc_only_if_necessary(meth, *mutators)
aliased_method_name = "__#{meth.object_id}__"
value = "@__#{meth.object_id}_value__"
dirty_flag = "@__#{meth.object_id}_dirty__"
module_eval <<-EOE
alias_method :#{aliased_method_name}, :#{meth}
private :#{aliased_method_name}
def #{meth}(*args, &blk)
#{dirty_flag} = true unless defined? #{dirty_flag}
return #{value} unless #{dirty_flag}
#{value} = #{aliased_method_name}(*args, &blk)
#{dirty_flag} = false
#{value}
end
EOE
mutators.each do |mutator|
aliased_mutator = "__#{meth.object_id}_#{mutator.object_id}__"
module_eval <<-EOE
alias_method :#{aliased_mutator}, :#{mutator}
private :#{aliased_mutator}
def #{mutator}(*args, &blk)
#{dirty_flag} = true
#{aliased_mutator}(*args, &blk)
end
EOE
end
end
# this hook is used to make the new method
# private to the extended class.
def self.extend_object(obj)
super
obj.private_class_method :recalc_only_if_necessary
end
end
Делая это доступно внутри определения класса, вы можете обернуть один или несколько методов легко без изменения существующего кода:
class Cashier
extend ExpensiveCalculation
def purchase(amount)
(@purchases ||= []) << amount
end
def total_cash
(@purchases || []).inject(0) {|sum,amount| sum + amount}
end
recalc_only_if_necessary :total_cash, :purchase
end
Это не могло бы иметь смысл, чтобы сделать что-то вроде этого, если вы просто хотите изменить один метод, но если у вас есть несколько, которые вы хотите изменить, то методы, подобные этому, могут быть весьма полезными.
Несвязанный комментарий: вы можете сделать 'inject (0,: +)', если хотите быть более кратким. –