4 Simple Memoization Patterns in Ruby

Memoization 是一种可以用来加速访问者方法的技术。将耗时的方法逻辑只需要运行一次,然后将结果缓存。Rails 以前曾有一个类似的模块来处理这个类似情况 memoize,后来因为争议而被删除了,取而代之的下面讨论的一些常见的 memoization 模式。

Super basic memoization

class User < ActiveRecord::Base
  def twitter_followers
    # assuming twitter_user.followers makes a network call
    @twitter_followers ||= twitter_user.followers
  end
end

Multi-line memoization

class User < ActiveRecord::Base
  def main_address
    @main_address ||= begin
      maybe_main_address = home_address if prefers_home_address?
      maybe_main_address = work_address unless maybe_main_address
      maybe_main_address = addresses.first unless maybe_main_address
    end
  end
end

begin...end 会创建一个代码块,Ruby 会将其当作一个整体来对待,所以 ||= 也会如期工作。

What about nil?

这个模式有一个比较讨厌的问题,在第一个例子中,如果用户没有 twitter 账户或者 twitter API 返回 nil 咋办?在第二个例子中,如果没有任何地址造成整个代码块返回 nil 咋办?

因为每次调用该方法时,变量均为 nil,所以后面的代码逻辑都会执行一次。这时候 ||= 就不是正确的选择了,我们需要区分 nilundefined

class User < ActiveRecord::Base
  def twitter_followers
    return @twitter_followers if defined? @twitter_followers
    @twitter_followers = twitter_user.followers
  end
end
class User < ActiveRecord::Base
  def main_address
    return @main_address if defined? @main_address
    @main_address = begin
      main_address = home_address if prefers_home_address?
      main_address ||= work_address
      main_address ||= addresses.first # some semi-sensible default
    end
  end
end

虽然这样的代码很难看,但是在 nil, false 和其它情况下都能正常工作。

And what about parameters?

现在访问器方法都能正常工作了,如何用这个技巧来处理带参数的方法呢?

class City < ActiveRecord::Base
  def self.top_cities(order_by)
    where(top_city: true).order(order_by).to_a
  end
end

我们可以用 Ruby 的 Hash 序列化技巧来处理这个问题。

Hash.new {|h, key| h[key] = some_calculated_value }
class City < ActiveRecord::Base
  def self.top_cities(order_by)
    @top_cities ||= Hash.new do |h, key|
      h[key] = where(top_city: true).order(key).to_a
    end
    @top_cities[order_by]
  end
end

不管在 order_by 中传递什么参数过来,都可以被缓存在 Hash 中。幸运的是,即便你传的参数是数组也没问题。

h = {}
h[["a", "b"]] = "c"
h[["a", "b"]] # => "c"

Why go through all this trouble?

但是,一旦你将这个技巧引入你的项目,并且咣咣咣的使用,那么代码很快会变得不可读。建议使用 memoist 来搞定。

原文链接

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Oct 15, 2018

奉献爱心