There are some basic pitfalls in our profession, in our art, that I'd like to call out. Much like musicians or visual artists that fall into old patterns or, similarly, attempt to be different for the sake of being different, software engineers often deal in convenient speech and comfortable habits. I offer the Singleton Pattern as an illustration.
Nearly any rudimentary book on program will take up the ageless wisdom of "GOTO considered harmful" and "Global variables are bad". These are fine points, but I constantly come across people that will trot out these soundbites and then turn around and use the Singleton pattern like it was on sale; like they were being entered into a sweepstakes every time they made a class a singleton.
Singletons are global state. Don't believe me? Then try and write a detailed description of that particular element of that pattern, then one regarding what a global variable is. Do you see how they're almost exactly the same? Yeah... there's a reason for that... it's because they're exactly the same.
Ambiguity of the Term
"Singleton" as a term can imply several things. Not so up on the latest vernacular, I refer to the two main approaches as simple and create on access, where simple refers to just a variable and accessor function to the instantiated object and create on access being that plus the addition of creating the object if it doesn't already exist from within the accessor.
The former case is just a variable. Let's not pretend that's really special. It's a static variable in the class. Why does it live in the class? It's really more a matter of organization than anything else. Would it be particularly different to store that as a "Global" in the same namespace? How about if you had a structure of objects that stored references to the single instances that your application needed? But really, all of that is highly irrelevant and any get the job done. However I've seen people bristle at the idea of storing that global state anywhere else.
How can someone be so comfortable with one and so disgusted by the other?
The next case is the create on access. It's easy for more junior level developers to think that this is pure win in that resources will not be allocated unless your program actually accesses those objects and the convenience factor of not having to explicitly create those instances. Unfortunately the reality of it is that you now have extremely little control over the order which your objects are created (assuming multiple singletons being implemented in your codebase). Innocently moving a chunk of code around could out and out change the order that subsystems are created and/or initialized. A cleaner approach to this would be to have a static initialization function in your class which handles the creation of your singleton and have the accessor warn/assert/etc. if it doesn't already exist. Or just go ahead and let the program crash. You were trying to access something before it was created and you should be getting a catostrophic failure.
Reading Makes Me Sleepy. What's Your Point?
Are you thinking critically about what you are actually organizing and how you're going about it? You should always be doing this.
- Moving around declarations/header files might drastically reduce your compile times
- You may have implemented one, if not many, extremely pointless interfaces and abstractions
- You're using an obscure component of the language to effectively do something that you know will work and that you could shortcut safely
- Your functions are way too large (hard to ascertain what they're doing)
- Your functions are way too small (hard to follow the chain of calls to see what they're doing)
- Your data models are wildly complex in order to deal with an edge case that you have no evidence of ever being an issue
- If you made a handful of, in the end, really minor concessions and constrained your system(s), you could represent everything with far less data and everything would be faster and more reliable.
#7 is a tough one to convince people of.
A vast majority of the time people will respond to your idea with "Yeah, but what happens when there are 3,000 objects?" or "What about if the designers want to have 200 options instead of 150?" That is pure conjecture. Is there any evidence that you need 3,000 objects or 200 options? Generally speaking, the sky is not the limit. Stop thinking and/or saying that. Look at the spec, come up with some simple and sane budgets, and move forward. Any truly hard limitations should be discussed with the entire team and everything else is likely somewhat flexible.
Servitude and Hubris Are Ugly
Take a step back from whatever you're creating and think about what it actually needs to do. No language was made to solve your exact problem. They're made to be, at least somewhat, generic. Following that logic it doesn't make much sense to blindly assume that the language or tool knows best for the problem you're working on. Of course you shouldn't just do something different for the sake of doing something different. Nor should you be assuming that there isn't something that you yourself are missing. But if you're not doing your due dilligence by thinking these things through you're not treating the task with enough respect.