说我有以下课程:
class Cashier
def purchase(amount)
(@purchases ||= []) << amount
end
def total_cash
(@purchases || []).inject(0) {|sum,amount| sum + amount}
end
end
这仅用于学习目的,请忽略这可能是多么不切实际。
现在一般来说,total_cash可能是一个昂贵的调用来遍历所有项目。
我想知道如果@purchases变量是脏的,即只有修改过的内容,我才能调用.inject。
如何修改我的课程呢?
答案 0 :(得分:1)
最简单的方法是维护另一个变量来指示@purchases
是否脏。例如:
class Cashier
def initialize(*args)
# init @purchases and @total_cash
@is_purchases_dirty = false
end
def purchase(amount)
(@purchases ||= []) << amount
@is_purchases_dirty = true
end
def total_cash
return @total_cash unless @is_purchases_dirty
@is_purchases_dirty = false
@total_cash = (@purchases || []).inject(0) {|sum,amount| sum + amount}
return @total_cash
end
end
更清晰/更简单的方法可能是每次为@total_cash
调用setter时计算purchases
。但是,这意味着您需要始终使用setter,即使在您的班级中也是如此。这也意味着您将在setter方法中“隐藏”昂贵的操作。你可以决定哪一个更好。
class Cashier
def purchase(amount)
(@purchases ||= []) << amount
@total_cash = (@purchases || []).inject(0) {|sum,amount| sum + amount}
end
def total_cash
@total_cash
end
end
我还建议您不要使用命名方案进行昂贵的操作。我会将total_cash
重命名为calc_total_cash
,以便告诉用户您的API,这是一个相对昂贵的调用,而不是简单的getter / setter。
答案 1 :(得分:1)
如果您愿意,可以比其他答案更进一步。您可以编写更改代码的代码,而不是将代码更改为仅在必要时重新计算。每个人都喜欢一点元编程。
这里有一些代码,它采用执行潜在长计算的方法的名称,以及调用时无效的任何先前计算的方法名称列表,并编写代码以包装方法,并仅在必要时执行计算,如果没有,则返回存储的值。
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
如果您只想更改一个方法,那么做这样的事情可能没有意义,但是如果您想要改变一些方法,那么像这样的技术可能非常有用。
答案 2 :(得分:0)
在最简单的情况下,您可以为要标记为脏的事物定义实例变量。修改变量时(在true
方法中)将其设置为purchase
。
检查total_cash
中的值;如果是这样,请使用总计的缓存版本。否则,计算新值并将其存储在缓存中。
class Cashier
def purchase(amount)
@purchases_dirty = true
(@purchases ||= []) << amount
end
def total_cash
@total_cash = (@purchases || []).inject(0) do |sum,amount|
sum + amount
end if (@purchases_dirty || @total_cash.nil?)
@purchases_dirty = false
@total_cash
end
end