Understanding the Singleton Pattern in Ruby

You are probably familiar with several design patterns that help structure and organize your code. One such pattern is the singleton pattern, which ensures that a class has only one instance and provides a global point of access to that instance. In this article, we will explore the Singleton pattern in Ruby, its implementation, and how to use it effectively in your projects.

What is the Singleton pattern?

The Singleton pattern falls into the category of creative design patterns and is used to prevent a class from instantiating multiple objects. Instead, it ensures that there is only one instance of the class, and it provides a way to access that instance from anywhere in your code.

The singleton pattern in Ruby

In Ruby, implementing the singleton pattern is straightforward. Let's start by examining a classic implementation without using the Singleton module:

class Bucket
  # Class variable to hold the single instance
  @@instance = nil

  # Private constructor to prevent direct instantiation
  private_class_method def initialize
    @items = [] # Initialize an empty array to store items in the bucket
  end

  # Method to get or create the single instance
  def self.instance
    if @@instance.nil?
      @@instance = new
    end
    @@instance
  end

  # Add an item to the bucket
  def add_item(item)
    @items << item
    puts "#{item} added to the bucket."
  end

  # Print the items in the bucket
  def print_items
    if @items.empty?
      puts "The bucket is empty."
    else
      puts "Items in the bucket:"
      @items.each { |item| puts item }
    end
  end
end

In this example, we have a Bucket class with a private constructor and an instance class method to create and return the single instance of the class. This ensures that no matter how many times you try to instantiate the Bucket class, you will always get the same instance.

bucket = Bucket.instance
bucket_second_instance = Bucket.instance

p bucket.equal?(bucket_second_instance) # => true
bucket.add_item("Apple")
bucket.add_item("Banana")
bucket_second_instance.add_item("Orange")

bucket.print_items # Print the items in the bucket
bucket_second_instance.print_items # Print the items in the bucket

As you can see, both bucket and bucket_second_instance point to the same object, confirming that the singleton pattern is in effect.

Refactoring using the module

Ruby provides a more elegant way to implement the singleton pattern using the Singleton module from the standard Ruby library. Here's how to refactor the Bucket class:

require 'singleton'

class Bucket
  include Singleton

  private_class_method def initialize
    @items = [] # Initialize an empty array to store items in the bucket
  end

  # Add an item to the bucket
  def add_item(item)
    @items << item
    puts "#{item} added to the bucket."
  end

  # Print the items in the bucket
  def print_items
    if @items.empty?
      puts "The bucket is empty."
    else
      puts "Items in the bucket:"
      @items.each { |item| puts item }
    end
  end
end

By including the Singleton module, you eliminate the need for a custom instance method, making the code cleaner and more idiomatic.

Summary

The singleton pattern in Ruby ensures that a class has only one instance, and provides a global point of access to that instance. You can implement it using a custom instance method or the Singleton module from the standard Ruby library. This pattern is useful when you want to control access to shared resources, configuration settings, or when you need to maintain a single point of control.
Now that you have a better understanding of the Singleton pattern in Ruby, consider how it can simplify your code and improve its maintainability in your future projects.