Ashley Davis Ashley Davis is an experienced software developer and author. He is CTO of Sortal and helps businesses manage their digital assets using machine learning.

You’re wrong about singletons

9 min read 2568

You're Wrong About Singletons

Singletons. They’re your worst nightmare — or at least that’s what you’ve been led to believe.

Are they really so bad? Why are they considered evil? And were they always on the wrong side of public opinion?

Singletons have been called a design pattern. They have also been called an anti-pattern. So which is it? Surely it can’t be both.

Here’s my guilty confession: I still use singletons. But I found a way to mitigate the drawbacks, so I happily use singletons for their benefits without suffering their problems.

In this blog post, we examine where singletons came from, where it all went wrong, and what you can do now to make use of singletons for their originally intended benefits — without guilt or fear. Read on to find out more.

Background

Given the amount of fear and loathing that surrounds singletons, it might surprise you to know that they weren’t born evil. In fact, singletons were in common use for at least 10 years before notions of their being evil had filtered throughout the blogosphere. Let’s have a quick look at the history.

Singletons were introduced to the world in 1995 through the now-classic software development book Design Patterns by the “Gang of Four” (shown in Figure 1), although certainly the concept of the singleton (if not the actual name singleton) had been around for many years before this book was published.

Design Patterns by Gang of Four
Figure 1: My personal copy of the Design Patterns book by the Gang of Four. This book introduced the world to the singleton design pattern in 1995.

While researching for this post, I wanted to know exactly when the love for singletons stopped. By at least 2007, hate had blossomed. Here is the earliest (still online) blog post I could find. I also found this one on the Wayback Machine from 2008. Then fast-forward to 2020 for this one.

So singletons had enjoyed more than 10 years of use before the anger against them bubbled to the surface. That’s a good run, but now it’s been over 10 years since they were considered an anti-pattern. It made me wonder why we are still talking about this after so long. Shouldn’t people have stopped using singletons already?

Then I realized, I myself had never actually stopped using singletons. In fact, I still use them frequently. I knew how bad they were; the years of arguments hadn’t escaped my attention. Yet somehow I’d found a way to make singletons work.

We made a custom demo for .
No really. Click here to check it out.

My code today is cleaner and more elegant than it ever was. I use singletons, but I also do significant automated testing, and I am constantly refactoring my code easily and safely. How is it possible that the maligned singleton has not destroyed my development process?

Development for me is a constant process of improvement and evolution. Yes, singletons have caused problems for me from time to time, but did I throw them out? No, because singletons are still useful. Indeed, that’s why people still use them; if people weren’t still using them, we wouldn’t still be arguing about them.

Instead of throwing out the singleton design pattern, I evolved it. I learned the problems with it (firsthand) and I adjusted how I used it. I found a way to use singletons without suffering the (by now) well known drawbacks. In a moment I’ll show you how.

What is a singleton?

Let’s start with a simple overview of the singleton pattern, just to get you up to speed.

Sometimes when coding, we need the concept of a global object. This is an object that only has one instance in our application. Design Patterns uses the following examples: printer spooler, file system, and window manager. There can and should be only one instance for these types of objects.

The singleton design pattern makes the class responsible for its own creation and controls access to the instance so that its single-instance nature cannot be subverted. We can therefore ensure this object is never created more than once.

The singleton is one of a handful of creational patterns covered in Design Patterns. It’s just one of a number of methods for creating objects.

Basic Singleton
Figure 2: Illustration of the traditional singleton design pattern.

Modern examples of singletons

To understand what a singleton is and how it’s useful, let’s consider some more modern examples of objects that can be represented well as singletons.

Dialog provider

One good example is the dialog provider. A UI-based application can display dialogs to collect input from the user. It makes sense that we only have one instance of our dialog provider so we can control how it’s used. For example, we probably want to enforce only one dialog on screen at a time.

Figure 3 illustrates how we might use a dialog provider as a singleton to easily and quickly connect it to deeply nested objects in your UI hierarchy.

The code below in Listings 1 and 2 is an example of how this dialog provider might be coded in JavaScript.

Dialog Provider as Singleton
Figure 3: Directly connecting deeply nested objects in our UI hierarchy to a singleton dialog provider.

Entity repository

Here’s another example that might appeal to you. Almost every application needs some form of data storage, and this is often implemented using the repository pattern. It can be very tempting to store our respiratory objects as singletons so that they are easily accessible from anywhere in our code.

This isn’t just for convenience, though: having a singleton instance of our entity repository means we have one place to implement caching for our entities and optimize so that subsequent data loads don’t have to go to the file system or database.

Listing 1: An example singleton implementation for our dialog provider in TypeScript
export class DialogProvider {
 
    //
    // Gets the singleton instance.
    // Lazily creates the singleton when first called.
    //
    public static getInstance(): DialogProvider {
        if (!this.instance) {
            this.instance = new DialogProvider();
        }
 
        return this.instance;
    }
 
    //
    // Instance of the singleton, after it has been created.
    //
    private static instance?: DialogProvider;
 
    // 
    // Presents the dialog box to the user.
    //
    public async showDialog(question: string): Promise<string> {
        // ... code here to display the dialog box ....
    }
 
    //
    // ... other functions go here ...
    //
}
Listing 2: Example of using the singleton
string question = ...
string answer = await DialogProvider.getInstance().showDialog(question);
// ... do something with the answer received from the user ...

Wiring dependencies

Traditionally, when wiring up dependencies through our codebase, we had two choices:

  1. Wire dependencies all the way through our potentially deeply nested code structure (see Figure 4 for an illustration)
  2. Directly access the dependency as a global object
Passing Through Dialog Provider
Figure 4: When not using a singleton for the dialog provider, we must wire this dependency all the way through our deeply nested UI hierarchy.

The first option is tedious and painful, and such hardwiring makes it difficult to restructure our application.

The second option, directly accessing a global object, is much easier but, again, makes it difficult to restructure our application.

Arguably, the second option is better. Both alternatives lead to hardwired code that is difficult to modify. But the second is easier to put in place, and there is less wiring to change later — because we don’t have to wire it through all the intermediate layers.

But globals are bad, right? Well, not so much back in the days when the singleton was invented.

Back then, computer programs were not quite as large and complicated as they are now, and automated testing was rare. The singleton design pattern introduces control over access while still retaining the convenience of having direct access from anywhere in our codebase. To an extent, the singleton design pattern legitimized the use of global objects.

The problems start

Over the years, our computer programs became larger and more complex. The teams developing them grew bigger. Automated testing became popular.

The singleton design pattern was overused and was probably often misused. The problems with the singleton manifested to the point where it became known as an anti-pattern.

A singleton by itself is hardly better than just accessing a global object, with all the problems that implies:

  • Objects that depend on singletons are not easily isolated for testing
  • Our codebase is hardwired, and it’s not easy to restructure it
  • Changing from a global object to a non-global object (if we decide that singletons are wrong in a particular case) is particularly difficult. Imagine having to wire it all the way through your codebase

Singletons (indeed any global references) and side effects are probably the biggest reason why legacy applications are difficult to restructure and difficult to adapt to automated testing.

You’re using singletons the wrong way

Let’s just face it — coding is difficult. Every design pattern, every technique, every best practice can be used the wrong way, and it can be overused. One coder’s design pattern is another’s anti-pattern.

The singleton is no exception.

The thing is, you are using singletons the wrong way. In most cases, we probably don’t even care if there’s a singleton instance, we mostly just want the convenience of an easily accessible object when that makes sense (and later, when it no longer makes sense, we’d like an easy way to rectify the situation).

We’d also like the convenience of not having to worry about startup ordering issues. Ideally, we just want start up dependencies to resolve themselves and figure out their own order of initialization. That’s something awesome we got from self-creating lazily instanced singletons.

So generally, we’d like the convenience of the singleton without having to take on any of the negative stuff. Is there a way to get the benefits of the singleton without the drawbacks?

Yes, there most certainly is!

Fixing singletons

Singletons are just so damn convenient. There is a reason people are still using them!

How can we use singletons but still be able to do automated testing and have an architecture that’s amenable to restructuring?

We can salvage the singleton, and it’s easier than you might think. Let’s make some changes to it:

  1. The singleton class itself should not be responsible for its own creation
  2. Other classes should not be hard-linked to the singleton

Solving these problems isn’t that difficult, but what we really need for it to be as convenient as the original singleton is for the wiring of dependencies to be automatic. We don’t want to have to wire through a dependency all the way through our codebase to get it to be everywhere that it needs to be accessed. Such manual wiring is tedious and is the opposite of convenience.

What we need is another design pattern — something that can automate the wiring of dependencies within our codebase.

DI saves the day

The good news is that dependency injection (DI), a design pattern that came a bit later, saves the day for singletons. Singletons coupled with DI gives us the convenience of singletons without the remorse or guilt (see example code in Listings 3 and 4 using the Fusion DI library).

Automatic dependency injection is specifically what I’m talking about; sometimes it’s called inversion of control (IoC). It automates the creation and wiring-through of our dependencies.

We can use DI to wire our global objects (aka singletons) through our codebase without having to do any manual setup. This automation makes it trivial to rewrite and restructure the connections between components in our application, even when those connections are to singletons.

When a dependency is injected into an object, that object does not need to know that it is actually connected to a singleton! Then, for automated testing, we inject a mock object as the dependency instead of the real object. This means we can do automated testing against objects that depend on singletons.

Automated DI also figures out the initialization order for our application. It automatically and lazily instantiates dependencies and dependencies of dependencies and creates them in the right order and at the right time, just before they are needed.

Singletons on their own no longer need to manage their own creation. The DI framework manages their creation, so singletons can be instantiated just like normal objects, and we can therefore instantiate them in our automated tests and run tests against them.

The problems with singletons have evaporated!

Now, some would argue that what I’m describing is simply DI and not singletons at all.

Well, that’s just semantics. I would argue that this is an evolution of how we create and consume global objects; it’s an evolution of how we use singletons.

From my perspective, I never stopped using singletons. I even still call them singletons in the DI library I created for TypeScript (Listing 3 shows how a singleton is defined using the Fusion DI library).

Listing 3: Example of a dependency-injectable, lazily created singleton in TypeScript
import { InjectableSingleton } from "@codecapers/fusion";
 
export interface IDialogProvider {
    // 
    // Presents the dialog box to the user.
    //
    showDialog(): Promise<void>
}
 
@InjectableSingleton("IDialogProvider")
export class DialogProvider implements IDialogProvider {
 
    // 
    // Presents the dialog box to the user.
    //
    public async showDialog(): Promise<void> {
        // ... code here to display the dialog box ....
    }
 
    //
    // ... other functions go here ...
    //
}
Listing 4: Example of dependency-injecting a lazily created singleton into a TypeScript class
import { InjectProperty } from "@codecapers/fusion";

export class SomeUIComponent {

    @InjectProperty("IDialogProvider")
    dialogProvider!: IDialogProvider;

    // ... other code here ...

    public async onButtonClicked(): Promise<void> {
        await this.dialogProvider.showDialog();
    }
}

To learn more about the Fusion DI framework, you can read my earlier blog post.

Conclusion

Singletons have been considered both a design pattern and an anti-pattern, but you need to remember that one person’s design pattern is another’s anti-pattern.

All design patterns can be applied to the wrong situations (wherein they become an anti-pattern) and all design patterns that are misused or overused and can cause damage. I want you to come away from this blog post with an understanding that it’s not all black and white. There are many shades of gray.

Arguably, the singleton is the most overused and badly applied design pattern, and that’s why it has suffered the backlash that it has received. But don’t just believe what you hear; you need to be able to think about these things for yourself. Think critically, and try it out before you form an opinion on it.

There is a reason why people are still complaining about singletons! It’s because they are still being used, even after 10 years of being considered evil!

Why are singletons still being used? Is it because some devs didn’t get the memo that singletons are bad? No, it’s because singletons are actually convenient and useful despite the various potential drawbacks. If devs weren’t using singletons, we simply wouldn’t be hearing about them anymore.

If you are going to use singletons, please make sure you are also using dependency injection. DI saves the day for singletons. Using DI means we can have global singleton objects and can benefit from automated dependency wiring and the ability to isolate using mocking to enable automated testing.

We can use the singleton design pattern for its originally intended benefits without exposing ourselves to the risks normally associated with singletons.

So stop worrying and just use singletons.*

*Make sure you are using DI as well, though.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Ashley Davis Ashley Davis is an experienced software developer and author. He is CTO of Sortal and helps businesses manage their digital assets using machine learning.

Leave a Reply