Design Patterns in TypeScript — Singleton (Part 1 of 5)

Mastering the best (or worst) practices via design patterns

AndyRum
The Startup

--

This article covers singleton design patterns, do’s and don’ts

TL;DR; Here is the repository with the complete solution.

What is the singleton pattern?

According to Wikipedia

…the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance. This is useful when exactly one object is needed to coordinate actions across the system. The term comes from the mathematical concept of a singleton.

Why do we need this?

When I started to learn OOP (object-oriented programming), I was stunned by how many things I can create thanks to classes. I used them everywhere, wrapped everything into classes, initiated new instances all the time. At that time I was working with PHP mostly and after a couple of weeks, I faced performance issues. I tried to figure out what went wrong and I found out there are some classes that working with shared resources, like database and cache. Moreover, I figured out, that my cache is not working as it is supposed to be: plenty of times the cache was empty, but I’m sure I put data into it. After that, the recognition just blew my mind:

Every class has its own state.

It is clear almost for everyone who used classes ever. But I loved my pretty classes, so I didn’t want to erase them for good, I wanted to keep them.

After some research, I met with the singleton design pattern. Lots of people complaining about the singleton is an anti-pattern, so you should NEVER use it. NEVER. Interesting… If there is something which takes credits in books and universities teach it, then why should I never use it?

Let’s write some code and dig in deeper what’s the truth.

As I mentioned before, I had multiple cache instances, so I couldn’t get the data I want. What’s the solution? Use only one instance of the cache. But how can it be ensured that a class has only one instance?

Let’s create a class

Multiple instances don’t share data

Pretty simple, huh? Just a class that has a map inside it, and we can manipulate that map with ease. But wait… there is a constructor, so we can accidentally create infinitely caches.

Why can I create more than 1 instance? Because it has a constructor. Let’s hide it!

In most languages change the constructor to private and create a static method that gives back the same instance over and over.

Singleton initialization

Notice that the class contains a private static field with the same type as the class, that instantly gets an instance of the class. The constructor is private, so we shouldn’t be able to create a new instance outside of the class (we will talk about it later). And finally again a static member: getInstance(). It just simply returns the private static instance.

TypeScript warns that can’t create a new instance, getInstance gives the same.

As you can see TypeScript takes care of our silly mistake with the class initialization.

If you’re building a multi-thread application that invokes the getInstance at the same time, there could be race conditions, that could result in the creation of multiple instances of the class. To avoid this, we can use lazy initialization.

Singleton lazy initialization

From the outside nothing has changed, we can use the class the same as before. The private instance initialization just moved into getInstance() with some benefits:

  • prevent race-conditions
  • the instance is created when the static method is first invoked

Are we done?

Uhm… Not yet.

TypeScript is a superset of JavaScript. It extends JavaScript by adding types to the language. So basically we’re running JS code at the end of the day. The tsconfig.json and eslint or the deprecated tslint could be confusing sometimes, so if the compiler doesn’t fail at compiling or at least warn us about the class initialization, we can get compiled JavaScript code:

The compiled JS code can create an instance

And guess what… You can run this “without” any issue! What happened?

In ES6 classes the members don’t have access level. Everything is just public. ES2020 introduced private access level via # character for properties and methods as well, but because of the browsers, we won’t see ES2020 soon on every website.

So yes, basically the code is useless, it can’t prevent anyone to create a new instance of the cache.

What can we do then?

Use the benefits of modules and module caching! When you require() anything, then the V8 engine will cache the required object. If you use require again, the object will come from the require cache. With this feature, we can easily create a singleton instance of our example cache!

Try it!

Use Node.JS module system caching mechanism to create a singleton

You just have to import the CacheService that is actually only an instance of the Cache class, and you can use it.

Because of the require built-in cache mechanism, we don’t have to worry about race conditions, the instance creation will occur only once.

One more thing

Cool, I have finally a global cache which is blazingly fast! How can I use it?

Remember, only the instance is exported, the class itself is not! Thanks to this there is a new issue: when using CacheService there aren’t types.

Oh crap! But I want to use my fancy strictly typed methods!

It’s easier than you think. Create an interface for it:

Use interface to get the typings for the hidden class

The interface can be exported because they are vanishing after compiling, but they can help us how to use the singleton class.

The final class with strict types can be found on GitHub.

What did we achieve with this?

The singleton design pattern solves problems like:

How can it be ensured that a class has only one instance?

Just export an instance, not the class itself.

How can the sole instance of a class be accessed easily?

Import the instance.

How can a class control its instantiation?

Actually, we can’t, because the constructor can’t be private. Okay… It can be via usual JS hacking. Don’t do this, please.

How can the number of instances of a class be restricted?

The import cache takes care of the number of instances.

How can a global variable be accessed?

Import the instance.

When to use this design pattern?

When you have a shared resource like a database/cache/a piece of hardware (robotic arms!). Of course, you can use your Logger as a singleton, but in most cases a static class fairly enough.

The connection to the database in a singleton class can avoid too many connections. In a NodeJS environment, your app is running constantly, so you don’t have to reconnect to the DB every time (good old days with PHP). You can use multiple connections like a connection pool, but you can still create the connection pool in the singleton.

The choice is yours, but don’t forget! Don’t overuse this pattern. As Abraham Maslow said:

“I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.”

Problems with singleton

This pattern is generally considered as an “Anti-pattern” as I mentioned before, for the following reasons:

  1. You cannot inherit from a Singleton Class. To add new functionality you cannot descend a new class to contain that functionality, breaking Separation of Concern.
  2. You do not have control over creation. When you access the instance of the class you cannot tell if it is an existing instance or a new instance that is returned.
  3. You cannot do Dependency Injection. As there is only a single instance of the class you cannot inject a dependency into the class. If done via a property you are changing the dependency for all references to that instance.
  4. As you have no control over creation, you cannot use a “Clean” instance of the object for each test.
  5. Without Dependency Injection, you cannot use Mock Objects in your tests.

Before using this pattern consider if you need any of these. If the answer is yes, then you have to rethink the solution.

Conclusion

The singleton pattern is not evil or good. It’s just a way to solve the problems in your code. Use it wisely and carefully ;)

--

--

AndyRum
The Startup

Passionate backend programmer and deadpool fan