[フレーム]
Last Updated: February 25, 2016
·
915
· davidmn2

Ruby - Memoization proxy class

I've needed to create a proxy class that would cache the method results from an object because I couldn't change its implementation.

It had a lot of expensive operations that would be done for every method call, and some of these heavy methods would be called internally, and that's why I needed to come with an "intrusive" solution.

It's important to note that you probably want to avoid using something like this, you probably will always prefer to design your code better over to using this hack. Also, it's needless to say that this will give you problems in case your methods are not referentially transparent or produce side effects.

class MethodCacheProxy

 instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }

 attr_reader :target, :cache, :invasive

 def initialize(target, invasive = false)
 @target = target
 @cache = {}
 @invasive = invasive
 intercept_instance_methods if invasive
 end

 def method_missing(name, *args, &block)
 cache_for(name, *args, &block)
 end

 def cache_for(name, *args, &block)
 unless block_given?
 cache[cache_key_for(name, args)] ||= target.send(name, *args)
 else
 target.send(name, *args, &block)
 end
 end

 def cache_key_for(name, args)
 "#{name}_#{args.hash}"
 end

 private

 def intercept_instance_methods
 instance_methods.each do |name|
 override_method(name)
 end
 end

 def instance_methods
 target.class.instance_methods(false)
 end

 def instance_method(name)
 target.class.instance_method(name)
 end

 def override_method(name)
 cacher = self
 method = instance_method(name)

 target.define_singleton_method(name) do |*args, &block|
 unless block.present?
 cache_key = cacher.cache_key_for(name, args)
 cacher.cache[cache_key] ||= method.bind(self).(*args, &block)
 else
 method.bind(self).(*args, &block)
 end
 end
 end
end

AltStyle によって変換されたページ (->オリジナル) /