Skip to main content

Command Palette

Search for a command to run...

Singleton Design Pattern

Published
4 min readView as Markdown
G
I am a developer with learnings in many different languages, frameworks and technologies.

Hello, in this article, I will explain the Singleton Design Pattern in depth, but I will explain it to you in very simple terms.

When in any context you think that a particular entity will be only one, shared among all other things use singleton design pattern for that.

Based on the above definition, you may think it is very easy and understandable, but I will tell you what it hides underneath.

Let’s first consider the following example:

We are making a library management system, so our obvious thought would be that there will be a library and inside that there will be a shelf, books, and a person who will handle the borrowing and returning of books. Now, as per our current thought process, we can clearly say that there will be a single library, so why not use the singleton design pattern? And that is correct, we can use the singleton design pattern, and that is as follows:

// Singleton Design Pattern Simple Implementation:
class Library {
    private static final Library INSTANCE = new Library();

    private final Map<String, Book> books = new HashMap<>();
    private final Map<Integer, Member> members = new HashMap<>();

    private Library() {}

    public static Library getInstance() { return INSTANCE; }

    public void addBook(Book book) {
        books.put(book.getIsbn(), book);
    }

    public void registerMember(Member member) {
        members.put(member.getId(), member);
    }

    public void showSummary() {
        System.out.println("Books in Library: " + books.size());
        System.out.println("Number of Members: " + members.size());
    }
}

The above example shows a correct, simple implementation of the Singleton Design Pattern, but it has two flaws in it:

  1. It is not thread-safe in lazy loading (thread safe for eager initialization): if you create more than one thread, it will create one instance of the library per thread, which will break our business model that only one library should exist.

  2. No Lazy Loading: As soon as the library class is created, the Library instance will be created, which means that even if we do not need it now, memory is already allocated. This is okay for small classes, but it can create issues with large classes.

We can use the following approach to solve the above two flaws:

// Singleton Design Pattern Bill Pugh Implementation
// Thread Safe Without Synchronization
// Lazy Initialization
class Library {
    private final Map<String, Book> books = new HashMap<>();
    private final Map<Integer, Member> members = new HashMap<>();

    // Private constructor
    private Library() {}

    // Inner static helper class holds the singleton instance
    private static class LibraryHolder {
        private static final Library INSTANCE = new Library();
    }

    // Public accessor
    public static Library getInstance() {
        return LibraryHolder.INSTANCE;
    }

    public void addBook(Book book) {
        books.put(book.getIsbn(), book);
    }

    public void registerMember(Member member) {
        members.put(member.getId(), member);
    }

    public void showSummary() {
        System.out.println("Books in Library: " + books.size());
        System.out.println("Number of Members: " + members.size());
    }
}

Here, it is guaranteed that Java loads the static inner class only once, irrespective of threads, and unless you call the getInstance() method, no object of type Library will be created.

And that’s it! Congratulations, you've learned the Singleton Design Pattern.

But now, an important question: who told you that the Library will always be the only one to exist? What if our library management system has multiple libraries as branches of the library?

Then our whole Singleton Design Pattern breaks, and it should break, because context matters. If our domain or business model says that there will be more than one library, we should never use the Singleton Design Pattern, even if we think that the Library is only one. Domain or Business model is the truth to make the system, not our thinking.

Advantages:

  1. Controlled access to only one instance: we can ensure only one instance exists, and it is shared with everyone, which means easy management of shared resources.

  2. Reduce memory footprint: As only one instance exists instead of multiple, there will be less memory footprint.

  3. Global access point: you just need to call the getInstance() method, and that’s it, use it anywhere you want.

  4. Useful for resources that require a consistent state across the system.

Disadvantages:

  1. Tight Coupling: Due to the getInstance() and other relevant methods used anywhere in the system, the system is tightly coupled.

  2. Difficult unit testing: as the resource is being shared across the system, when writing a test case, it is very difficult to reset the state every time a new test case is checked.

  3. If used blindly, it violates SRP: a singleton class should still have a single responsibility; otherwise, it violates the single responsibility principle.

  4. Very difficult to implement for a distributed system: a different coordinator service will be required if there is more than one instance of the same machine running. As a singleton will break there.

  5. Not flexible for future expansion: if in future we need to change something in the singleton class, then we will require a complete rewrite of the code where it is invoked. Also, if we later on, as per the business model, want to switch from singleton to multiple, it will be a complete rewrite.

So, the Singleton Design Pattern can create wonders if used wisely, as it is powerful, but at the same time, it is dangerous.

For most of the modern applications, direct object management and dependency injection are often flexible and safer alternatives.

More from this blog

The iamgautam03 Blog

23 posts

A growing collection of articles on software development, system design (HLD & LLD), and practical coding tips. I focus on making complex ideas simple, so developers can learn faster and build better.