In my last post on building Class Diagrams in PlantText, I mentioned I’d be kicking off a Design Patterns series. I figured we’d start simple and then build from there. You can’t get much simpler than the singleton pattern, right? So here we go. Just don’t ask ChatGPT to generate a singleton pattern for you (at least without refining your question), because it’ll barf out some very clumsy code. I argued with ChatGPT a bunch while writing this post actually.
Before jumping in I wanted to mention that I really enjoyed this article by Jon Skeet (from his book C# in Depth). He examines the singleton pattern in C# and comes up with 6 versions that are commonly written. They range from sloppy ones that’ll barely get the job done, to perfect implementations for any situation. It is funny how something so simple can be so complex. After reading his article, you might wonder: Will the PlantUML for the singleton pattern be mostly the same for all of his 6 versions? You’ll just have to read to the end here to find out!
What
The singleton pattern is used all the time and in lots of different ways, so it’s a good one to start with. Sure, it’s often seen as problematic because it can be referenced globally and can easily be made to violate the Single Responsibility Principle in your applications. But it solves real problems.
How about we start with a definition of the singleton? The singleton pattern is a software design pattern that restricts the instantiation of a class to a singular unique instance. So it ensures…
- Only one instance of the class is ever created in an application space
- That there is direct and easy access to that one instance
- That the object manages it own instantiation
If you read a lot of different development gurus, they’ll add other best practice details to this list, like…
- The unique instance should be thread safe
- The class should be lazy as well (load on demand, not at creation)
- It should prevent other classes from inheriting from it (must be sealed)
Why
Singletons can be problematic but they are important when you’re modelling something that should have only one instance and centrally manage that data and logic. A singleton puts all the global variables in one place and makes sure that there can only be one instance of an object managing those “variables.” That makes global variables less likely to cause problems. You can even have some logic in the instance to protect the usage of those variables.
Just be disciplined in how you use the singleton. Referencing your singleton in every class throughout your API will get you into trouble. In layman’s terms, I try to pass the data and logic of singletons to other classes through constructors and method arguments as much as possible, even though you can just reference them directly anywhere in a class. You may remember that whole “high cohesion, low coupling” and SOLID principles stuff from school. If not just google it or have a nice little conversation with ChatGPT about it.
When
More importantly, when would you think about using the singleton pattern in real world applications? Well here are a few examples provided by ChatGPT…
- Database Connection Manager: In a system that interacts with a database, you may want to ensure that there is only one instance of the database connection manager throughout the application. This is because creating multiple database connections can be expensive and may lead to performance issues. By using the Singleton pattern, you can ensure that all database requests go through a single connection manager, thereby improving performance.
- Configuration Manager: In a system that has multiple configuration files, you may want to ensure that there is only one instance of the configuration manager throughout the application. This is because multiple instances of the configuration manager can cause inconsistencies in the configuration data. By using the Singleton pattern, you can ensure that there is only one source of configuration data throughout the application.
- Logger: In a system that requires logging, you may want to ensure that there is only one instance of the logger throughout the application. This is because multiple instances of the logger can cause conflicts in the log data. By using the Singleton pattern, you can ensure that there is only one source of log data throughout the application.
- Cache Manager: In a system that uses caching, you may want to ensure that there is only one instance of the cache manager throughout the application. This is because multiple instances of the cache manager can cause conflicts in the cached data. By using the Singleton pattern, you can ensure that there is only one source of cached data throughout the application.
This probably seems overly obvious to you, but it’s a pretty good list I thought. I was surprised that ChatGPT left out “Load Balancer,” but that’s just me.
How
Oh, yeah, this article is supposed to be about PlantUML, so let’s get back to that. Below you can see the basic PlantUML for a singleton, which has 1) a static property to hold the single unique instance that is created and 2) a static method for getting the instance so it can be used in client code. Wow, simple…why did I write this article!?
@startuml class Singleton { -{static} Singleton _instance +{static} Instance(): Singleton } @enduml
Singletons in the Wild
What follows next are a couple of the most common implementations you tend to see in real code in the wild. We’ve all seen and probably built these classes hundreds of times in our careers. Yeah, they may be sloppy, but they usually get the job done.
public sealed class Singleton { private static Singleton _instance = null; private Singleton() {} public static Singleton Instance { get { if (_instance == null) { _instance = new Singleton(); } return _instance; } } }
public sealed class Singleton { private static Singleton _instance = null; private static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { lock (padlock) { if (_instance == null) { _instance = new Singleton(); } return _instance; } } } }
I thought that was all you needed, but the truth is we can analyze this to death just like anything else…and many people have. After reading all the gurus’ thoughts on the singleton, I realized there are some additional things to think about. Take a look at the version below that is thread safe, lazy loading, and also very simple and elegant. I had never heard of the “Lazy” class in C# so that was new to me. I’ll probably start using it all the time now.
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } }
Above, when client code calls the property “Instance”, it always returns the single unique instance of Singleton. And Singleton is created using the Lazy object via a lambda expression calling Singleton’s own constructor. So clean and simple. No need for conditionals, locks, nested classes, etc. Very functional, efficient, clean, and simple.
Anyway, the C# code is cool, but does the PlantUML for the singleton pattern change at all for any of these different implementations? Big reveal… No, of course not. And that’s the point when you’re trying to communicate the intent of your design. In a UML diagram, we don’t care about the implementation and all of its details and complexity. That’s the beauty of it! Let’s keep it simple so we can all talk and be on the same page, regardless of our experience level and badassness.
Oh so pretty, let’s look at it again!