Patterns in Ruby: Singleton Pattern

The Singleton pattern is the black sheep of the pattern family. It was easy to grasp, developers everywhere applied it liberally, and an inevitable backlash came against its overuse.

I won't make any judgments or reccomendations on when to use it - but I will show you just how easy it is to apply in Ruby.

The literal translation of the pattern is to create a class level instance method and to hide the new method.

    
class Example
  def initialize
    # do something?
  end

  def self.instance
    return @@instance if defined? @@instance
    @@instance = new
  end
  private_class_method  :new
end

puts Example.instance.object_id  #=> 21783380
puts Example.instance.object_id  #=> 21783380

This example gives you the basic idea, but it doesn't cover many cases you'd like to handle, like cloning or duping the singleton. It also doesn't hide the class level allocate method, which means a sneaky coder could still create another instance through some hacking. Lastly, it's not thread safe.

Luckily, Ruby already provides a module for making classes singletons. It's in the standard library, inside 'singleton.rb'. Here's how you use it:

    
require 'singleton'

class Example
  include Singleton
end

This module will do the same thing as my example above but will also handle hiding allocate, overriding the clone and dup methods, and is thread safe. The library file itself contains a bunch of examples of its usage, and those interested should definitely read through it.

One thing to note about these implementations is that the instance method takes no arguments, so none are passed on to the object's constructor. This makes sense because the first time instance is called those will be the arguments used for this global instance. Setters are typically more appropriate for most singletons.

Since singletons are global in nature setters should be at the class level. As an extra bonus here's the implementation of the class level attr_ methods to generate the vanilla getter/setter methods (stolen from Rails).

class Class # :nodoc:
  def cattr_reader(*syms)
    syms.flatten.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end

        def self.#{sym}
          @@#{sym}
        end

        def #{sym}
          @@#{sym}
        end
      EOS
    end
  end

  def cattr_writer(*syms)
    syms.flatten.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end

        def self.#{sym}=(obj)
          @@#{sym} = obj
        end

        def #{sym}=(obj)
          @@#{sym} = obj
        end
      EOS
    end
  end

  def cattr_accessor(*syms)
    cattr_reader(*syms)
    cattr_writer(*syms)
  end
end

Now we can create a more realistic singleton:

    
require 'singleton'

class JimmyGrimble
  include Singleton
  cattr_reader :boots
  cattr_accessor :football
end

About this entry

Advertise here!