| Description: |
# Multiton module that ensures only one object to be allocated for a given
# argument list.
#
# The 'multiton' pattern is similar to a singleton, but instead of only one
# instance, there are several similar instances. it's usefull when you want to
# avoid constructing objects many times because of some huge expence (connecting
# to a database for example), require a set of similar but not identical
# objects, and cannot easily control how many time a contructor may be called.
#
# Usage:
# class SomeMultitonClass
# include Multiton
# attr :arg
# def initialize(arg)
# @arg = arg
# end
# end
#
# a = SomeMultitonClass.instance(4)
# b = SomeMultitonClass.instance(4) # a and b are same object
# c = SomeMultitonClass.instance(2) # c is a different object
#
# in previous versions the Class.new method was made private, but this restriction
# has been lifted with the following caveat: Class.instance will attempt to
# retreive a previously cached object, constructing one if needed, but Class.new
# will *always* construct a new object - this should allow great flexiblity with
# multiton classes
module Multiton
# pools of objects cached on class type
POOLS = {}
# method which can be defined by a class to determine object uniqueness
MULTITON_ID_HOOK = :multiton_id
def Multiton.append_features(klass)
def klass.instance(*args, &block)
# we try to use a previously cached object, if one is not found
# we construct one and cache it in the pool based on class and
# the args given to the contructor
#
# note: a limitation of this approach is that it is impossible to
# detect if different blocks were given to a contructor (if it takes a
# block. thus, it is the constructor *args _exclusively_ which determine
# the uniqueness of an object. a workaround is for the class to define
# the _class_ method multiton_id, eg:
#
# def Klass.multiton_id(*args, &block)
#
# end
#
# which should return a hash key used to identify the object being
# begin constructed as (not-)unique (see examples below)
#
k =
begin
# if the class defined 'multiton_id' we use
# this as the key
send MULTITON_ID_HOOK, *args, &block
rescue NameError => ne
# otherwise we simply use the argument list
args
end
unless (obj = (POOLS[self] ||= {})[k])
begin
critical = Thread.critical
Thread.critical = true
obj = (POOLS[self][k] = new(*args, &block))
ensure
Thread.critical = critical # restore state
end
end
obj
end
end
end
if __FILE__ == $0
#
# EXAMPLE A - STANDARD USAGE
#
class SomeMultitonClass
include Multiton
attr :arg
def initialize(arg)
@arg = arg
end
end
a = SomeMultitonClass.instance(4)
b = SomeMultitonClass.instance(4) # a and b are same object
c = SomeMultitonClass.instance(2) # c is a different object
puts a == b # -> true
puts [a.arg,b.arg].max * 10 + c.arg # -> 42
#
# EXAMPLE B - MODIFY AN EXISTING CLASS, SHARED FILEHANDLES
#
lineno = __LINE__
# forty
# two
class File; include Multiton; end
a = File.instance(__FILE__)
b = File.instance(__FILE__)
p a == b # -> true
lineno.times{a.gets}
puts a.gets # -> " # forty
puts b.gets # -> " # two
#
# EXAMPLE C - INHERITENCE
#
class A < String
include Multiton
end
# B is also a multiton - with it's OWN object cache
class B < A; end
# w is the same object as x, y is the same object as z
w,x,y,z = A.instance('A'), A.instance('A'), B.instance('B'), B.instance('B')
p w.id == x.id # -> true
p y.id == z.id # -> true
a = B.instance('A')
b = B.instance('A')
p a.id == w.id # -> false (each class has it's own pool)
p a.id == b.id # -> true
#
# EXAMPLE D - MULTIPLE MODULE INCLUSION (does nothing)
#
yz_id = y.id || z.id
class B
include Multiton
end
# y is not only the same object as z, but they are both the same object(s)
# as from EXAMPLE C
y,z = B.instance('B'), B.instance('B')
p y.id == yz_id # -> true
p z.id == yz_id # -> true
#
# EXAMPLE E - SO YOU WANNA USE NEW INSTEAD OF INSTANCE...
#
module K
# use an inner class which is itself a multiton
class K < String; include Multiton; end
# define a new which returns a mutltion using #instance...
class << self
def new(*args, &block)
K.instance *args, &block
end
end
end
the = K.new '4'
answer = K.new '2'
printf "%s%s\n", the, answer # -> 42
puts the.class # -> K::K
#
# EXAMPLE F - using Klass.multiton_id
#
class Klass
include Multiton
def initialize important, not_important
@important, @not_important = important, not_important
end
def Klass.multiton_id(*args, &block)
# we consider only the first arg
important, not_important = *args
important
end
end
a = Klass.instance :a, :b
b = Klass.instance :a, :c
c = Klass.instance :b, :b
p a == b # -> true
p a == c # -> false
p b == c # -> false
end # if $0 == __FILE__
__END__
# output should be
true
42
true
# forty
# two
true
true
false
true
true
true
42
K::K
true
false
false
|