Reputation: 16015
Let's say I have a class Foo
and the constructor takes 2 parameters.
Based on these parameters the initialize method does some heavy calculations and stores them as variables in the instance of the class. Object created.
Now I want to optimize this and create a cache of these objects. When creating a new Foo object, I want to return a existing one from the cache if the parameters match. How can I do this?
I currently have a self.new_using_cache(param1, param2)
, but I would love to have this integrated in the normal Foo.new()
.
Is this possible in any way?
I can also deduct that using .new()
combined with a cache is not really syntactical correct.
That would mean that the method should be called new_or_from_cache()
.
clarification It's not just about the heavy calculation, it's also preferred because of limiting the amount of duplicate objects. I don't want 5000 objects in memory, when I can have 50 unique ones from a cache. So I really need to customize the .new method, not just the cached values.
Upvotes: 2
Views: 1159
Reputation: 146143
As you probably know you have reinvented the factory method design pattern and it's a perfectly valid solution using your name for the factory method. In fact, it's probably better to do it without redefining new
if anyone else is going to have to understand it.
But, it can be done. Here is my take:
class Test
@@cache = {}
class << self
alias_method :real_new, :new
end
def self.new p1
o = @@cache[p1]
if o
s = "returning cached object"
else
@@cache[p1] = o = real_new(p1)
s = "created new object"
end
puts "%s (%d: %x)" % [s, p1, o.object_id]
o
end
def initialize p
puts "(initialize #{p})"
end
end
Test.new 1
Test.new 2
Test.new 1
Test.new 2
Test.new 3
And this results in:
(initialize 1)
created new object (1: 81176de0)
(initialize 2)
created new object (2: 81176d54)
returning cached object (1: 81176de0)
returning cached object (2: 81176d54)
(initialize 3)
Upvotes: 0
Reputation: 187134
class Foo
@@cache = {}
def self.new(value)
if @@cache[value]
@@cache[value]
else
@@cache[value] = super(value)
end
end
def initialize(value)
@value = value
end
end
puts Foo.new(1).object_id #2148123860
puts Foo.new(2).object_id #2148123820 (different from first instance)
puts Foo.new(1).object_id #2148123860 (same as first instance)
You can actually define self.new
, then call super
if you actually want to use Class#new
.
Also, this totally approach prevents any instantiation from ever occurring if a new instance isn't actually needed. This is die to the fact the initialize method doesn't actually make the decision.
Upvotes: 5
Reputation: 168199
Something like this?
class Foo
@@cache = {}
def initialize prm1, prm2
if @@cache.key?([prm1, prm2]) then @prm1, @prm2 = @@cache[[prm1, prm2]] else
@prm1 = ...
@prm2 = ...
@@cache[[prm1, prm2]] = [@prm1, @prm2]
end
end
end
Edited
To not create an instance when the parameters are the same as before,
class Foo
@@cache = {}
def self.new prm1, prm2
return if @@cache.key?([prm1, prm2])
@prm1 = ...
@prm2 = ...
@@cache[[prm1, prm2]] = [@prm1, @prm2]
super
end
end
p Foo.new(1, 2)
p Foo.new(3, 4)
p Foo.new(1, 2)
# => #<Foo:0x897c4f0>
# => #<Foo:0x897c478>
# => nil
Upvotes: 1
Reputation: 158
Here's a solution I came up with by defining a generic caching module. The module expects your class to implement the "retrieve_from_cache" and "store_in_cache" methods. If those methods don't exist, it doesn't attempt to do any fancy caching.
module CacheInitializer
def new(*args)
if respond_to?(:retrieve_from_cache) &&
cache_hit = retrieve_from_cache(*args)
cache_hit
else
object = super
store_in_cache(object, *args) if respond_to?(:store_in_cache)
object
end
end
end
class MyObject
attr_accessor :foo, :bar
extend CacheInitializer
@cache = {}
def initialize(foo, bar)
@foo = foo
@bar = bar
end
def self.retrieve_from_cache(foo, bar)
# grab the object from the cache
@cache[cache_key(foo, bar)]
end
def self.store_in_cache(object, foo, bar)
# write back to cache
@cache[cache_key(foo, bar)] = object
end
private
def self.cache_key(foo, bar)
foo + bar
end
end
Upvotes: 1
Reputation: 66837
You could use a class-level instance variable to store results from previous object instantiations:
class Foo
@object_cache = {}
def initialize(param1, param2)
@foo1 = @object_cache[param1] || @object_cache[param1] = expensive_calculation
@foo2 = @object_cache[param2] || @object_cache[param2] = expensive_calculation
end
private
def expensive_calculation
...
enf
end
Upvotes: 0