Singleton Pattern? No! Use this instead 💡
Tags: beginners, programming, java, designpatterns
Published: 2023-09-27 08:36:28

The Singleton Pattern is extremely popular among beginners in Object-Oriented Programming due to its ease of implementation and promise of global state handling, but is it worth it?

## Table of Contents
- [What is the Singleton Pattern](#what-is-the-singleton-pattern)
- [The first problem](#the-first-problem)
- [Singleton breaks a SOLID principle](#singleton-breaks-a-solid-principle)
- [Dependency Injection](#dependency-injection)
- [A better alternative: Monostate](#a-better-alternative-monostate)
- [Be aware that global state can be unpredictable](#be-aware-that-global-state-can-be-unpredictable)
- [Conclusion](#conclusion)
- [References](#references)

## What is the Singleton Pattern

Simply put, the Singleton Pattern **ensures that a class has only one instance while providing a global access point to that instance.** You can imagine that it could be useful to share a configuration object across an application, for example.

To create a singleton, we will need to add a couple of things to our class:
- First, **we add a private static field for storing the singleton instance.**

```java
private static Singleton instance;
```

- Second, we **make the constructor private**, so our client can't instantiate it whenever it pleases.

```java
private Singleton(String value) {
   this.value = value;
}
```

- If we can't instantiate it outside of itself, we need to **create a public static method for getting the instance.**

```java
public static Singleton getInstance(String value) {
    instance = new Singleton(value);
    return instance;
}
```

- We now instantiate a new object only on its first call and **attribute it to our static field**, so the method will always return the same instance, no matter how many calls are made. So the previous code become:

```java
public static Singleton getInstance(String value) {
    if (instance == null) {
        instance = new Singleton(value);
    }
    return instance;
}
```

Let's check the full code in Java to visualize it better:

```java
public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}
```

We can check if it is working with the following:

```java
public static void main(String[] args) {
    Singleton singleton1 = Singleton.getInstance("PIPIPI");
    Singleton singleton2 = Singleton.getInstance("POPOPO");
}
```

Note that we didn't directly instantiate it.

Here, both `singleton1.value` and `singleton2.value` would have the same value `"PIPIPI"` because the object was created on the first call only, and the second one can only access the cached object.

That's it! Well... it depends.

## The first problem

This implementation particularly is too simplistic for **multi-threaded languages** like Java because different threads could create different instances simultaneously, so it would not be a Singleton anymore, right? If we need some sort of slow initialization, we will run into problems.

To prevent race conditions like that we need to synchronize threads when instantiating the singleton. I won't get into details here because the solution requires double-checked locking and that's another topic. Also, Java handles it in its very own way and it requires some extra knowledge.

[Refactor Guru](https://refactoring.guru/design-patterns/singleton/java/example#example-2) provides a very interesting implementation of a thread-safe singleton and explains pretty well the strategy behind double-checked locking. Check it out for further discussion.

## Singleton breaks a SOLID principle
Our class is breaking the **Single Responsibility Principle** by trying to solve two problems at the same time: Ensure that it is instantiated only once and provide a global access point to that instance.

A class exists to serve as a blueprint of an object, and not to also instantiate the object itself.

## Dependency Injection

Since we use Singletons to expose global state, it makes no sense to inject them into other objects, right? But with this approach you end up hiding the dependencies of your application in your code, **instead of exposing them through interfaces**, and we should [code against interfaces, not implementations](https://betterprogramming.pub/code-against-interfaces-not-implementations-37b30e7ab992).

Without dependency injection, our singletons and the classes that need him are tight-coupled, **creating a huge problem when unit testing.**

## A better alternative: Monostate

Also known as BorgIdiom (because the Borgs in the Star Trek series share a common memory), the Monostate Pattern allows the creation of **multiple objects that share the same static attributes** instead of guaranteeing that only a single instance of a class exists. 

```java
public static void main(String[] args) {
	Monostate monostate1 = new Monostate();
	Monostate monostate2 = new Monostate();
	monostate1.setValue("Apple");
	monostate2.setValue("Banana");

	System.out.println(monostate1.getValue());
	System.out.println(monostate2.getValue());
	System.out.println(monostate1 == monostate2);
}
```

Both values will be "Banana" but `monostate1 == monostate2` will be false, because they are not the same object.

Monostate has one major advantage over singleton: The subclasses might decorate the shared state as they wish and hence can provide dynamically different behavior than the base class.

Users of a monostate do not behave differently than users of a regular object. The users do not need to know that the object is monostate, and that's one of the most important characteristics.

## Be aware that global state can be unpredictable
Sharing the same state across an application and expose an API to modify it, can very easily lead to confusion since it gets almost impossible to know what is the current value once the setter is called in different places.

Let's re-create the first Singleton example with a setter, so we can assign a new `value` not only in the constructor but anytime.

```java
public final class Singleton {
    private static Singleton instance;
    public static String value;

    private Singleton(String value) {
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }

    public static void setValue(String newValue) {
        value = newValue;
    }
}
```

For the sake of simplicity, the example is all in the entry point of the app:

```java
public static void main(String[] args) {
	Singleton singleton1 = Singleton.getInstance("PIPIPI");
        Singleton singleton2 = Singleton.getInstance("LALALA");
	singleton2.setValue("POPOPO");
	System.out.println(singleton1.value);
	System.out.println(singleton2.value);
}
```

Both print `"POPOPO"` because there is only one instance! So if we have a `singleton3`, `singleton4`, and so on (or simply Singleton. You don't need to assign a static class to a new variable) anywhere in our app, it's really difficult to keep track of our current state value.

It is even worse if the value type is a reference type like an Array, ArrayList, or any Iterable! Just think about the number of `nullPointerExceptions` that it could create, or the unexpected elements inside.

On that matter, I honestly believe functional programming brings a better approach with the use of pure functions, but that's a discussion for the future.

## Conclusion
We cannot use a design pattern blindly. It can solve a problem while creating others if we don't have an understanding of the pros and cons.

Also, SOLID principles are not sacred but they've been tested over decades and developed conventions proven to create better maintainability for huge codebases, so I particularly tend to respect them a lot for the code quality they brought to my career.

Thank you so much for reading.

## References
[https://refactoring.guru/design-patterns/singleton](https://refactoring.guru/design-patterns/singleton)
[https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/](https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/)
[https://www.youtube.com/watch?v=yimeXZ1twWs](https://www.youtube.com/watch?v=yimeXZ1twWs)
[https://stackoverflow.com/questions/137975/what-are-drawbacks-or-disadvantages-of-singleton-pattern](https://stackoverflow.com/questions/137975/what-are-drawbacks-or-disadvantages-of-singleton-pattern)
[https://jorudolph.wordpress.com/2009/11/22/singleton-considerations/](https://jorudolph.wordpress.com/2009/11/22/singleton-considerations/)
[https://betterprogramming.pub/code-against-interfaces-not-implementations-37b30e7ab992](https://betterprogramming.pub/code-against-interfaces-not-implementations-37b30e7ab992)
[https://www.simplethread.com/the-monostate-pattern/](https://www.simplethread.com/the-monostate-pattern/)