Fork me on GitHub

Project Notes

#376 Singletons in Ruby

Using the singleton pattern in Ruby

Notes

Some things are unique. The singleton pattern is used to implement a class that can only have a single instance.

The Singleton creational patterns was first made famous by the “Gang of Four” in Design Patterns: Elements of Reusable Object-Oriented Software. A ruby take is covered in Design Patterns in Ruby, Chapter 12: Making Sure There Is Only One with the Singleton.

Doing it By Hand

By making the new method private, we prevent anyone from creating an object using the conventional constructor. Then we can use a class variable to record and return the same instance whenever requested.

See amateur_soloist.rb for an implementation:

class AmateurSoloist
  @@instance = AmateurSoloist.new
  private_class_method :new

  def self.instance
    @@instance
  end

  def to_s
    "Amateur Soloist [object_id=#{object_id}]"
  end
end

if __FILE__==$PROGRAM_NAME
  (ARGV[0] || 2).to_i.times do |i|
    soloist = AmateurSoloist.instance
    puts "Instance #{i + 1}: #{soloist}"
  end
end

And it works as expected:

$ ruby amateur_soloist.rb 4
Instance 1: Amateur Soloist [object_id=60]
Instance 2: Amateur Soloist [object_id=60]
Instance 3: Amateur Soloist [object_id=60]
Instance 4: Amateur Soloist [object_id=60]

I’ve added accompanying tests in test_amateur_soloist.rb:

$ ruby test_amateur_soloist.rb
Run options: --seed 63601

# Running:

....

Finished in 0.000223s, 17937.2197 runs/s, 17937.2197 assertions/s.

4 runs, 4 assertions, 0 failures, 0 errors, 0 skips

There are limitations: it’s not thread safe for example. Instead adding thread safety ourselves, we can instead use the Singleton mixin supplied with Ruby

Using Singleton Mixin

The Singleton module implements the Singleton pattern.

See pro_soloist.rb for an implementation:

class ProSoloist
  include Singleton

  def to_s
    "Pro Soloist [object_id=#{object_id}]"
  end
end

if __FILE__==$PROGRAM_NAME
  (ARGV[0] || 2).to_i.times do |i|
    soloist = ProSoloist.instance
    puts "Instance #{i + 1}: #{soloist}"
  end
end

And it works as expected:

$ ruby pro_soloist.rb 3
Instance 1: Pro Soloist [object_id=60]
Instance 2: Pro Soloist [object_id=60]
Instance 3: Pro Soloist [object_id=60]

I’ve added accompanying tests in test_pro_soloist.rb:

$ ruby test_pro_soloist.rb
Run options: --seed 9829

# Running:

....

Finished in 0.000254s, 15748.0315 runs/s, 15748.0315 assertions/s.

4 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Considerations

The singleton pattern only really works in the context of a single thread or process (with a thread-safe implementation).

If one needs a true “singleton” in a multi-process or distributed scenario (such as in a web service), then other approaches are required such as semaphores or database locks.

Credits and References

About LCK#376 rubydesign patterns

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader