Singleton pattern
Created: 12/6/2021Updated: 12/14/2021
Join me in implementing a singleton from scratch, and discussing why you might want to use one.
Definition
The singleton pattern is defined in Design Patterns [1], as a "creational" pattern intended to, "Ensure a class only has one instance, and provide a global point of access to it."
Diagram
What's the value?
The most obvious answer could be one of practicality. For instance, your business rules may dictate that a "foo" only has one "bar", a game only one board, etc. A singleton allows you to codify and enforce that business rule, vice having some global variable holding the "one" reference somewhere and hoping a new one isn't instantiated.
You might also decide that a single instance of a given "service" is all an application needs. Sharing a single service instance is also an effective way of sharing state throughout an application.
Consider how a singleton could help you provide caching functionality by wrapping a dependency like fetch in the browser or an HttpClient in .Net or another framework.
Building one from scratch
So, how do we build a singleton? How do we ensure that a class can only have one instance at runtime and provide a global point of access to it?
If you're familiar with dependency injection (DI) or inversion of control (IOC) containers, you may be thinking of how you might declare a class a singleton in one of those. Cast that out of your mind for now. The method we're about to go over requires no dependencies or third party libraries; just a language that supports classes and the static keyword.
The below example is in TypeScript, however, with a few semantic changes, you can have the same implementation up and running in C#, Java, and most other object oriented languages.
Consider the below TypeScript singleton implementation:
exportclassSingleton { privatestaticinstance: Singleton | null = null; publicstaticgetInstance(): Singleton { returnthis.instance || (this.instance = newSingleton()); } privateconstructor() { }}
Line one defines a static variable, "instance" on the Singleton class with a default value of null. This variable is used by the public static property "Instance" on the same class. Whenever Singleton.Instance is referenced the value of "instance" will be returned if it has already been assigned, or else assigned then immediately returned. This behavior of the first reference triggering the single instance's creation is referred to as lazy loading, and is a pattern of its own.
Because static places these class members in the ownership of the class itself, as opposed to a given instance, the usage of Singleton.Instance can be trusted to always return the single runtime instance of Singleton.
Now, there's just one more loose end to tie up; the constructor. It wouldn't do for us to go to all that trouble with those "instance" members if we allowed construction of Singletons anywhere outside the Singleton class. Adding the "private" keyword to the constructor takes care of that, causing any use of new Singleton() outside of this class to throw a compilation error.
A couple additions
You might be wondering how you should go about clearing out the one instance if the occasion calls for it. As an example, you may wish to start with a fresh instance for each unit test. Here's an easy addition to give you this flexibility, without violating the intent of the pattern:
publicstaticDestroy = () =>Singleton.instance = null;
What if your singleton has dependencies you want to inject? In the above example, we use a property for "Instance" which doesn't take any parameters. When you need to pass in parameters to "Instance" presumably to pass them on to the constructor, you can just drop the "get" keyword. At least that's how we would do it in our TypeScript example. In C#, Java, etc. you would otherwise convert your "Instance" property to a method.
Example:
export classSingleton { privatestatic instance: Singleton | null = null; publicstaticInstance(someParam: string): Singleton { returnthis.instance || (this.instance = new Singleton(someParam)); } privateconstructor(private someParam: string) { } publicGetSomeParamValue(): string { returnthis.someParam; }}
Best practice for instance params
One of the benefits of the singleton pattern is that you don't have to concern yourself with where the singleton is used first. In your code, you can just reference Singleton.Instance whenever you need to and go about your business. You can screw this up if you're not careful with how you handle params being passed to your global access point, Instance.
To prevent this from becoming a headache, follow these two best practices:
Only do this for the purpose of dependency injection
Default all injected dependencies
Defaulting another singleton would be pretty darn easy wouldn't it?... In fact, I highly recommend taking advantage of singletons to keep your classes smaller and injecting them into each other this way.
A more realistic example
So far the code examples have focused on the specific things needed to implement the pattern. Showing a proper use of the pattern may help put things into perspective. The blog you're currently reading uses a singleton service called, "Contentful" to retrieve post data. The implementation of that service is shown here:
exportinterfaceIContentful { GetEntries<T>(query: Record<string, string>): Promise<sdk.EntryCollection<T>>;}exportclassContentfulimplementsIContentful { privatestaticinstance: IContentful | null = null; publicstaticInstance( app = App.Instance(), client: sdk.ContentfulClientApi = sdk.createClient(params) ): IContentful { returnthis.instance || (this.instance = newContentful(client)); } publicstaticDestroy = () =>Contentful.instance = null; private entryCache = newMap<string, sdk.EntryCollection<any>>(); privateconstructor(private client: sdk.ContentfulClientApi ) { } publicasyncGetEntries<T>(query: Record<string, string>): Promise<sdk.EntryCollection<T>> { const cachedEntries = this.entryCache.get(JSON.stringify(query)); if (cachedEntries) { return cachedEntries; } else { const entries = awaitthis.client.getEntries<T>(query); this.entryCache.set(JSON.stringify(query), entries); return entries; } }}
This service abstracts interacting with an external sdk to retrieve blog posts. It also implements some simple runtime caching to cut down on how often posts are retrieved. If you navigate this site (without refreshing) with your network tab open you can confirm that each request that goes out to the blog is unique. Not only would more than a single instance be unnecessary, but the intent of the caching behavior would be subverted. Using the singleton pattern here ensures we don't end up with multiple caches.
You'll notice some dependencies being injected (one being a singleton itself), which allows for easy unit testing, but, because we default the dependencies we inject, using the above service is a simple as the below snippet:
const contentfulService = Contentful.Instance();const posts = await contentfulService.GetEntries<Post>({ content_type: ContentTypes.Post});
A question that is reasonable to ask at this point is, "why not use the static keyword on all the methods in a class, or just use a plain function and pass that around?" The short answer is that the singleton pattern plays more nicely with other OO principals and patterns. Also consider that you may either not have control over the interface you need to implement or lack the flexibility to change it. In such a case, using the singleton pattern won't require you to compromise the open-closed or dependency inversion principles.
Summary
This singleton is a common, simple, and powerful pattern that very clearly expresses design intent. Their lazy loaded construction make them extremely flexible and stable. They're very easy to use and test, and can even save you a bit of memory if you're into that sort of thing. Like other patterns, even if you find that you don't use them often, understanding them enough to recognize them will help you understand other developers' intent and articulate your own.