Multiton

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 is useful when you want to avoid constructing objects many times because of some huge expense (connecting to a database for example), require a set of similar but not identical objects, and cannot easily control how many times a contructor may be called.

Synopsis

  class SomeMultitonClass
    include Multiton
    attr :arg
    def initialize(arg)
      @arg = arg
    end
  end

  a = SomeMultitonClass.new(4)
  b = SomeMultitonClass.new(4)   # a and b are same object
  c = SomeMultitonClass.new(2)   # c is a different object

Previous Behavior

In previous versions of Multion the new method was made private and instance was used in its stay —just like Singleton. But this has proved less desirable for Multiton since Multitions can have multiple instances, not just one.

So instead Multiton now defines create as a private alias of the original new method (jus tin case it is needed) and then defines new to handle the multion, and instance is provided as an alias for it.

So if you must have the old behavior, all you need do is re-alias new to create and privatize it.

  class SomeMultitonClass
    include Multiton
    alias_method :new, :create
    private :new
    ...
  end

Then only instance will be available for creating the Multiton.

How It Works

A pool of objects is searched for 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.

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). So it is the constructor arguments only which determine the uniqueness of an object. To workaround this, define the class method ::multiton_id.

  def Klass.multiton_id(*args, &block)
    # ...
  end

Which should return a hash key used to identify the object being constructed as (not) unique.

Methods
Constants
POOLS = {}
  Pools of objects cached on class type.
MULTITON_ID_HOOK = :multiton_id
  Method which can be defined by a class to determine object uniqueness.
MULTITON_NEW_HOOK = :multiton_new
  Method which can be defined by a class to create multiton objects.
Public Class methods
append_features( klass )
# File lib/facets/more/multiton.rb, line 102
  def self.append_features( klass )

    class << klass

      alias_method :create, :new
      private :create

      def new(*args, &block)
        # if the class defined 'multiton_id' we use this as the key
        # otherwise we simply use the argument list.
        k = (respond_to?(MULTITON_ID_HOOK) ? send(MULTITON_ID_HOOK, *args, &block) : args)
        unless (obj = (POOLS[self] ||= {})[k])
          begin
            critical = Thread.critical
            Thread.critical = true
            if self.respond_to?(MULTITON_NEW_HOOK)
              obj = (POOLS[self][k] = self.send(MULTITON_NEW_HOOK, *args, &block))
            else
              obj = (POOLS[self][k] = super)
            end
          ensure
            Thread.critical = critical # restore state
          end
        end
        return obj
      end

      alias_method :instance, :new

    end

  end