Cover photo credit: Stanislav Ivanitskiy
Before we dive into refuting the use of singletons, let’s first define them.
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance. This is useful when exactly one object is needed to coordinate actions across the system. The term comes from the mathematical concept of a singleton. Wikipedia
To expand a bit, a singleton guarantees that there will only ever be a single instance of a given object or class available in the system.
- State management
Each of these benefit from only allowing a single copy of the class/object to exist. We gain certainty about the state of our system by only having a single location that manages a specific job.
This principal is a great way to consider how to maintain and develop models. It helps us ensure single responsibility to each of the models we create.
Singletons can fit very cleanly into the bijection principal.
Let’s examine the caching layer. For our example we’ll say we have a weather service that fetches the weather for the next 10 days. This data is perfect for a caching layer because it changes infrequently. In our pretend system we choose to decorate our weather service with a caching layer that will check to see if we already have weather data and return it from the cache if it has already been fetched.
The cache needs to be a singleton. If it were not then many places in our system could be calling through to the API potentially causing rate limiting issues for the application.
This singleton still serves a single purpose, and can be cleanly mapped to a real world object. Think of this as the weather man. He gathers the weather for you and keeps that knowledge himself disseminating to anyone who asks him.
Singletons inherently create a problem of coupling. It’s a simple truth that I cannot refute.
getInstance method during a test scenario and provide an appropriate mock or fake as a return value.
Moreover, testing the singleton itself is equally straight forward taking into account the appropriate tooling. Jest provides a nice helper
jest.isolateModules() which allows us to reload the module for each test invocation. There is also
mock-require which gives us a nice helper called
mock.reRequire() for the same purpose.
By focusing early on implementation issues (the Singleton is an implementation pattern) we orient ourselves according to accidentality (how) and underestimate the most important thing of an object: the responsibilities it has (what). - #3
This is a challenging one to refute. Early focus on implementation is something every software engineer needs to be cognizant of and work to manage. However to say that singletons somehow exacerbate this issue on their own is a bit presumptuous.
I would argue that a singleton does help to reduce the overall load being placed on the garbage collector be maintaining a single set of working memory.
Ummmm. Nope. You can just as easily have the
getInstance() method be invoked by a dependency injection mechanism as you can have a class that is “newed” up each time.
This argument seems flawed.
This is another spot that fall into the “if you do it poorly then it’s bad” mindset. If we allow the constructor of a singleton to be exposed without a safety guaranteeing that it was invoked from the
getInstance method of its class then we’re not implementing singletons properly.
I can’t continue to rebut all of the reasons provided in Maxi’s article.
We do not fall victim to the inability to test, nor the coupling that is burdened by a closed object system.
Singletons are great, for the right use case.