A Beginner’s Guide to Implementing the Singleton Design Pattern in Ruby

Design patterns #1

Muralish Clinton
Better Programming

--

Photo by Henrik Dønnestad on Unsplash

When working with Object-Oriented Programming, have you ever wondered why we have to always create an instance of a class? Sometimes, based on the use case, we might require only one instance, which can be used globally after initialization.

I’ve seen cases where people declare the instance of a class, assign it to a global variable upon the project’s boot up, and use it throughout the project. Is there a better way other than using a global variable? Yes, Singleton classes!

Singleton is a creational design pattern that is originally a part of the Gangs of Four Design Patterns.

What is a Singleton Design Pattern?

Gangs of Four is a collection of 23 design patterns from the book “Design Patterns: Elements of Reusable Object-Oriented Software.” As mentioned in the book, patterns can be broken down into three main categories: creational, behavioral, and structural. The Singleton pattern is a creational pattern as it deals with the creation of an object.

The pattern basically restricts the initialization of the class to ensure that only one instance of the class can be created and used throughout the application.

Use cases of the Singleton pattern

As defined previously, the Singleton pattern ensures that the entire implementation of a class revolves around the only instance, and this can be really useful when designing certain use cases.

There are several cases when this pattern can be really useful, but I’ve highlighted three of the most widely used use cases below:

  1. Log service
  2. Cache service
  3. Database service

In all of the cases above, a single instance need not be defined during the initialization of the project, and the methods of the class can be accessed via the predefined instance, which can be used throughout the application to perform the defined actions. As there’s only one instance of the class at any given time, a lot of memory is saved.

Example

Case 1: Basic implementation of classes

Before we implement the singleton pattern, let’s look at how an instance of a class is created and used throughout the application.

As shown above, an instance of the config class can be created every time an action is required to be performed in the class.

Case 2: Singleton pattern

As shown above, you could implement the singleton pattern by doing class << self. This allows you to specialize the behavior of methods called on that specific object. You can reference a method in the class without creating any instance objects explicitly.

Though this method looks very straightforward, it has to be written in a thread-safe manner. If not, it will cause problems in a multi-threaded environment (explained in detail in the next section).

Case 3: Singleton pattern with the built-in Ruby-Singleton mixin.

As shown above, we can directly make use of the default singleton ruby mixin which comes with the Ruby package. This is very straightforward to use as well and is implemented in a thread safe manner.

Thread Safe

The problem with Singleton is that when running parts of the application in multiple threads, there’s a possibility for more than one instance of a singleton class to be created due to race conditions. By the definition of the Singleton pattern, we only want one instance of a class during the application’s lifetime, and therefore the naive implementation of Singleton classes is not thread safe.

From the previous section, the third case, which makes use of Ruby’s Singleton mixin, is thread safe by default during initialization. However, we should take the responsibility of keeping our singleton methods implementation thread safe.

In the second case, we can make it thread safe by making use of Mutex — A Mutex is a mutually exclusive flag. It acts as a gatekeeper to a section of code, allowing one thread in and blocking access to all others.

To fix the underlying problem with threads in the naive implementation of Singleton, you have to synchronize threads during the first creation of the Singleton object, as shown in the code above.

Cons of Using Singleton

Though it looks fun and simple to implement, Singletons come with a few cons too:

  1. Difficult to test — As the constructor of the Singleton class is abstracted, you will have to mock the initialization during unit testing.
  2. Need to handle multi-threading cases carefully
  3. Not SOLID compliant — Does not solve one problem at a time

References

  1. Singleton Design Pattern | Implementation with details & code
  2. TutorialsPoint — Singleton
  3. Ruby Singleton

--

--