When you want an object that can only ever have a single instance in an application, and is also easy to access, you might spend some time perplexed at how to accomplish this task. However, if the class itself could be responsible for initialization of it's instance, we could use a static method to obtain that instance on command! This is the main idea of the Singleton pattern.
Assumptions
- Basic programming proficiency
- Knowledge of <a href="https://www.devmaking.com/learn/design-patterns/design-pattern-modeling/" target="_blank" style="color:inherit;">Modeling Notation</a>
What is a Singleton?
The singleton design pattern ensures that there is only one instance of itself in an application, and that it is easy to access from anywhere. Singletons are great for accessing and managing data that should only ever exist in one place within an application.
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/SingletonPattern_01.png" alt="singleton pattern UML diagram" style="max-width:95%;"> </div>
The singleton brings together the advantages of a static class and non-static class; it can be accessed from anywhere without a dependency like a static class, and it can implement interfaces like a non-static class.
> While a singleton can also be used to maintain global application state, it is discouraged to use it in this way as it becomes akin to a global variable. However, this does not mean it should never be used in this way; there are plenty of perfectly acceptable use-cases, some of which show the singleton to be the best tool for the job!
A Typical Singleton
Usually, a singleton will be implemented so that it can be retrieved with a GetInstance()
method in the class. The reason for this is because the constructor is hidden away from the client to prevent more than one instance being present in the application.
Although, we can take it one step further, making the GetInstance()
method private, meaning that only the singleton class is allowed to modify it's state. This means that only public static methods can get information about the singleton, which forces the singleton class to act as more of a self-contained utility.
class Singleton {
// Static, private instance to hide it from the world:
private static Singleton instance;
// Singleton-instance data:
private Logger logger
// Private constructor:
private Singleton() {
this.logger = new Logger();
}
// Private instance getter for use by the static methods: (3)
private static Singleton getInstance() {
if(instance is null) {
instance = new Singleton();
}
return instance;
}
// Static method for client access:
public static void logSomething(String message) {
// Get the singleton instance:
Singleton manager = Singleton.GetInstance();
// Log the message using our imaginary logger class:
manager.logger.log(message);
}
}
If we use a singleton class as a logging system for an application, we can toggle it on or off based on application settings:
class Singleton {
//...
boolean loggingEnabled;
private Singleton() {
if(Application.debugMode || Application.CurrentPlatform == "Editor") {
loggingEnabled = true;
}
else loggingEnabled = false;
}
//...
public static void logSomething(String message) {
Singleton manager = Singleton.GetInstance();
if(manager.loggingEnabled) {
//...
}
}
}
With this, we'll only get log messages when developing or deploying a test run of the application!
Singletons in practice
Singletons have earned a reputation for being an anti-pattern because of their overuse by novice developers and ability to make code harder to manage over time.
Arguments against Singletons usually involve preferring Dependency Injection, a fancy word for passing references to objects around instead of allowing global accessors. This is in itself a powerful concept in Object-Oriented Development, but there are times where this can become too cumbersome in a given design, which is where a Singleton can offer more freedom.
From personal experience in game development projects, Singletons are usually paired well with Object Pools to create instance-managers.
One such example is a texture-management system in a game engine; the application has a Singleton instance that holds the data for all of the textures currently loaded in, so when a texture is needed by the graphics system, it doesn't have to worry about loading in a duplicate image; this is one of the ways modern games are able to create amazing visuals while also maintaining the memory budget.
For a more advanced example: implementing a custom memory management system can greatly improve speed in high-performance applications (such as video games), especially if you use the native memory API for the target device. One example is the WIN32 library; even on 64x architecture, it's possible to get speeds 3-4x faster than the default language memory management. If you implement a system that manages custom heaps to fully exploit the memory usage, you'll likely need to use a singleton to access the memory manager from around the application!
At the end of the day, the answer to life, the universe, and everything, is: ... <s>42</s> it depends! As the designer, you are responsible for making the best decision for a given design. If using a singleton makes life easier than using an alternative like dependency injection, then use what you believe is the best tool for the job, but I would advise against going around turning everything into a singleton!
Recommended Resources
- <a target="_blank" href="https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=devmaking-20&linkId=bc32087110669f75d93b216df79816f0" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Head First Design Patterns: A Brain-Friendly Guide</a> : An excellent primer for learning design patterns in a pragmatic way.
- <a target="_blank" href="https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=devmaking-20&linkId=ae25d94c4ea49870eb3115e0e4d2de90" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Design Patterns: Elements of Reusable Object-Oriented Software</a> : The reference guide made famous by Gang of Four, still widely used today.
- <a href="https://www.draw.io" target="_blank" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Draw.io</a>: A free, open-source tool for designing diagrams with built-in support for UML diagrams to make your own!