19

I'm currently writing an MVC application and I need a class that can:

A: get, add and remove data(specifically a TreeSet of sorted strings that I want stored in memory, but I doubt the data itself is relevant.)

B: The data must be accessible to multiple controllers.

C: There can "presumably" only be one instance of this class. These controllers will often share this set.

D: Since the class will be accessed by multiple controllers, the class must be thread-safe.

It seems like an enum singleton would work for this problem but I've read that singletons are an anti-pattern and I'm curious as to possible alternatives?

I was thinking of maybe abstracting over this by creating one or multiple "API" classes that can modify the state of a single non-global instance(protected singleton?) containing our data and then passing these API classes into their respective controllers via dependency injection, but I feel like this may be kicking the can down the road? I genuinely don't know.

2
  • 6
    Singleton pattern biggest problem is causing issues for testing and/or mocking. Because you might want to substitute your cache for different implementations, but the access call might be hardcoded. But you can build code with this in mind.
    – ontrack
    Mar 13 at 15:49
  • 1
    Why do you call it a cache?
    – Sascha
    Mar 15 at 8:42

6 Answers 6

30

A singleton can mean two things:

  1. An object where only a single instance exist

  2. A particular pattern (from the infamous Design Patterns book) for accessing singletons through a static method on class.

Singleton in sense (1) are fine and useful. Pattern (2) is problematic because this is really a global variable with extra steps, and this has well-known problems for testing, modularization and maintainability.

An alternative to (2) would be dependency injection.

10
  • 16
    Your first meaning should say: An object where only a single instance can exist. If you just happen to have only one instance of an class, that's not a singleton; the singleness needs to be enforced somehow. And since only being able to instantiate a class once implies a need for a way to get the one instance, (2) is strongly implied by (1).
    – Caleb
    Mar 13 at 14:55
  • 11
    @Caleb If you go by that definition, virtually no singleton in a container would be a singleton, because you could easily create more of them - it's just convention (in production code) to not do so. The ability to easily create more instances is also exactly what makes it useful for testing where you may want a "singleton" per test (with multiple tests running in parallel even).
    – Voo
    Mar 13 at 15:52
  • 9
    Why "infamous" for the reference to GoF Design Patterns? Mar 13 at 17:03
  • 12
    @JaredSmith I believe it stems from the fact that certain people believe that everyone in the Java sphere must know these patterns, and since everyone knows them, some people interpret that as everyone must use them constantly. As a result they suffer from overuse. On top of it, some of them cause code obfuscation, testing difficulties, and other issues if not handled correctly. They can be good patterns if used correctly, but often they are not. Mar 13 at 19:31
  • 4
    @gnasher729: This is a general issue with globals, not just singletons. One benefit of dependency injections is that the dependencies of a class are explicit. If 50 different classes have a dependency on the same global, then you might have a bigger problem.
    – JacquesB
    Mar 14 at 17:46
17

I feel like the hate for the Singleton pattern is a little over-the-top. Yes, it can introduce some challenges, but it can also be a very simple solution in narrow circumstances. I think some of the invalid reasons for its bad name are:

  • Overwrought, unecessary, and broken implementations such as double-checked locking
  • Overuse. When Design Patterns were really hot, a lot of people learned exactly one pattern (Singleton) and used it all over the place where it wasn't appropriate at all.
  • Misunderstanding. Singleton, as typically implemented, is a simplification of the GoF pattern. E.g., the pattern shows a polymorphic implementation as an example.

That said, there are real issues. The main issue is testing. The standard Singleton (i.e., as typically written) is not easily replaceable with mocks or fakes in languages like Java.

The other primary issue is that if your assumption that there will never need to be more than one instance is wrong, or is no longer true in a future version, reworking the code to eliminate or enhance it can be challenging. Some of this can be mitigated by adding a (perhaps optional) parameter to your getInstance method and making it a 'multiton'.

My personal take is that the larger and more complex the application, the more problematic the approach becomes, and you should be using DI (with or without a framework.) If your application is relatively small and focused, introducing DI can be overkill. As long as you can accept the potential cost of reworking the code and your testing needs are met, I don't think it's the design kryptonite that it's usually made out to be.

6
  • 6
    None of the points listed are the real problem (even testing can be done with state snapshot). The Signelton is evil due to shared mutable state.
    – Basilevs
    Mar 14 at 8:50
  • 6
    @Basilevs Nothing about a Singleton requires that your state be mutable but I'd be interested to see you write an answer about a caching solution with no shared mutable state.
    – JimmyJames
    Mar 14 at 14:35
  • 3
    The great advantage of the singleton is the shared mutual state. If you don’t want shared mutual state, don’t blame the singleton, use some other way.
    – gnasher729
    Mar 14 at 16:54
  • @gnasher729 Singletons are an effective way to share mutable state but you could also share non-mutable state or just state-free behaviors using them. Moreover, DI (along with many other approaches) can also be used to share mutable state. If mutable state is 'the problem' with Singleton, then DI is just as 'evil'.
    – JimmyJames
    Mar 14 at 17:47
  • 3
    @JohnBollinger Interesting. Can you explain how the Singleton patten is more tightly coupled than, say the AbstractFactory? It's clear that the clients of the Singleton are coupled to it but I'm not certain that implies that the Singleton is coupled to its clients. It doesn't have any dependencies on them.
    – JimmyJames
    Mar 15 at 16:38
6

The singleton pattern, as described in the gang of four book, as it is mostly implemented in Java, can indeed be considered an antipattern. This includes static accessors, enum singletons, and so on, which are at most singletons in the scope of a single class loader.

However, if you write Java software today, you'll probably not start out with public static void main() and roll your own from there. Usually, you'll be using some kind of container to perform the setup of your application, and there is your alternative. In CDI, you declare a bean as @ApplicationScoped, and there's a similar scope in Spring.

Still a singleton, but not a roll-your-own implementation. And this is quite OKish, provided that you are prepared to deal with global state, which this still represents.

Note however, that this kind of singleon is still "once per container", so the setup is not scalable. If you intend to run multiple instances of your application together in a cluster, you need to find a different solution.

2
  • Is the use of multiple classloaders in a single application still a thing in 2024? I associate this with really obnoxious errors and I'm not sure what problem it solves that containerization doesn't solve in a more reliable and manageable way.
    – JimmyJames
    Mar 13 at 15:00
  • 1
    The problem with static singletons isn't that you have to write them yourselves. If you used a self-written IOC container (not much to it in its simplest form) that would still be fine. The main problem with "static accessor singletons" is that it makes testing cumbersome (hello static reset method) and impossible to parallelize.
    – Voo
    Mar 13 at 15:48
5

When I was first hired out of college, I started working with some software in the mil-sim sphere that is over 20 years old. Often times I was given a task and told to look at similar code in the program and emulate how it was coded in other parts, and build what they wanted the way it was already done. I would pour over dozens of classes and compare how each of them worked to come up with similar structures, and I saw everything from reflection, to factory of factories, to singletons.

When I finally decided on a solution, I would take it to one of my seniors and propose it to them. I remember the first time I suggested a singleton though. It was a reaction of dismay. Despite every single example I could find for what I wanted to do being in this pattern, my senior didn't like it. I didn't know why. We discussed it with him asking me why I wanted to use the pattern, and what I wanted to do. Eventually he realized that what I really needed was a single class only ever being instantiated once, and this pattern actually fit the bill. He signed off on it. He never explained why he found the concept so abhorrent.

I had many patterns in that code base that I learned over time to be wary of. Either I had to think and make sure that it was exactly what I needed, and had to learn why I would need to use that pattern, or I learned that the patterns in the code were being used wrongly and I needed a new pattern.

I don't think singletons are an antipattern. They can be difficult to work with and test, but if you need exactly what it provides, then you need a singleton. Nothing else will suffice. How you implement it, that's up to you. Though I've only learned the 20 year old one right now, I'm not above learning something new if it's better. Just remember, make sure when you make a singleton that you will never, ever, need more than one of the class. If you think you might need several, an object pool might be better, or just find a different pattern.

3
  • 2
    To one of the points in my answer, it was really common for junior devs to learn about Singleton and then start using it for almost everything and create a big mess that would need to be cleaned up. Based on your story, I'm guessing that was the senior's experience and the immediate reaction was to push back. There are definitely times when it's crucial to have one instance, at least within a certain context. Not just 'we only need one' but there 'must only be one' or things won't work. Memory management (GC) comes to mind as an example.
    – JimmyJames
    Mar 13 at 20:28
  • 1
    Yep. I recently reworked a library we passed out to a subcontractor to not expose any unnecessary code for our CV model selection, creation, and use. Eventually I had to settle on the factory pattern that caused so many issues for me in traceability in code in the past. They didn't need to know what model I gave them. They just told me where the models were, what size images they were going to give me, and they got one that would give them the list of bounding boxes they expected. Anything else was just way too complicated for them to deal with it turns out. Mar 13 at 21:27
  • 1
    Also, to the person who downvoted my post, why did you do so? If it was because you believe singletons are in fact an antipattern, please explain how? One of the qualities of antipatterns is that there is another documented and easily replaceable solution to the problem. There is not for singletons. There may be different kinds of singletons or ways to implement them, but all of them are singletons, and if you need to have exactly one of them for some reason, there is no work-around. Thus is it not an antipattern. Mar 13 at 21:36
3

While there are many situations where using a singleton cache may be more convenient than having to maintain and pass around a caching context, many situations may arise where such designs end up creating difficulties. If the purpose of a cache is to store things that are by nature genuinely immutable (e.g. numbers and their cube roots), and the cache does not allow outside observation of its state, then having the cache be a singleton would be unlikely to cause any trouble. If, however, a cache is meant to be attached to some mutable entity and track its state, then having the cache be a singleton will effectively compel the observed object to be a singleton as well.

0

No, singletons are not an anti-pattern, per se. Their function depends a bit on the programming language, especially on which features it provides to solve the problem of having a single instance of something around.

For all intents and purposes, true global state is an anti-pattern in almost all circumstances (exceptions may include things like embedded programs, quick throw-away "tooling"-style shell scripts and the like).

Singletons then allow you to get the features of global state without the drawbacks of global state. Sometimes your requirements absolutely do call for something that is around only once, and in a guaranteed manner. Singletons do that. They also - compared to global state - allow you to tightly control how that state can be instantiated, accessed and modified. All very useful properties.

In your case - static configuration - I see nothing wrong with it (it basically does not matter much). Also with dynamic (user-changeable) configuration. If your app supports that the user modifies a config file and automatically reloads that, then you would much rather have only a single time of reloading in your app, and not 50 classes having to reload their individual instances of your AppConfig class. This becomes ever more important the more expensive it is to work with the data (think DB accesses and so on and forth...).

As mentioned elsewhere, there may or may not be issues you have to solve; like how to exactly inject (instantiate) the singleton content, or what to do during testing. This depends on your general architectural choices, your preferences and so on and forth.

---- Rant on patterns ----

If you read that singletons are an anti-pattern, point blank, with no context, then that is rubbish. There are many abstract patterns out there, there are whole books solely discussing this topic (Design Patterns comes to mind).

It is an error to assume that the goal is to try hard to apply patterns wherever possible. Some devs can hardly write a class without applying three patterns of some kind or another on it before starting the first real line of code. This is a fallacy of course.

Patterns each solve a problem - you must first figure out if you have the problem at all, before turning to them. Knowing the most important patterns is still a very great thing for a dev: you can recognize them if you see them, and if your code calls for it, you already know about them, and can use them without coming up with some ad-hoc solution that's maybe not the greatest.

Also, these days we know how important it is to be able to refactor your code ruthlessly. It is absolutely fine to start out with a very direct, pattern-less implementation, and refactor later when you see that you are trying to repeat yourself, or if the codes starts to get unwieldy.

Using patterns for their own sake is like being a woodworker and trying to find a way to use a hammer for every single step of your project. You will hammer in screws; use your hammer to split boards, and so on and forth. This is bad of course. The good way is to have the hammer, and knows where it hangs, and be skilled in applying it when you need it.

3
  • 1
    You mention that singletons give you control over how state can be instantiated, accessed and modified. Are you referring to something similar to Spring-style singletons here? Because my experience is that a big part of the problem of Gang-of-four-style singletons is precisely that you cannot do these things, or at least can't do them any better than you could with static methods (static in the Java/C# sense).
    – James_pic
    Mar 15 at 10:08
  • 1
    Not sure what you mean. A singleton (implemented via a static accessor method) allows you to initialize the singleton object on first call; a static object would always be instantiated during loading of the class. Also since the lifecycle of the object is controlled by your code, you can implement whatever you wish on top of that (e.g. a method which reloads etc. the singleton, logging everything correctly, or whatever you want...)
    – AnoE
    Mar 15 at 10:42
  • I suppose my point is that instantiation on first call has never felt like significantly more control than instantiation during class loading. Either way you can't be sure whether something else will initialise the instance, or reason about what else will access or modify it.
    – James_pic
    Mar 15 at 11:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.