This is the fourth article in the design Patterns Adventure series, and I hope that each article in this series will help you understand each design pattern through easy to understand language descriptions or small examples from everyday life. Today’s article is to learn about the singleton model with you. After reading this article, you will definitely learn something.

With regard to the singleton pattern, this is probably the simplest of the design patterns. If you’ve studied design patterns, you probably forget a lot of design patterns when you don’t use them for a long time, but you certainly don’t forget singleton patterns. Because its theoretical knowledge is relatively simple, practice is also very convenient.

But can you really use singleton correctly? Do you know when singletons are appropriate and when they cause a lot of trouble? Or do you just use it as a global variable because it’s easier to develop and doesn’t require a lot of code? In today’s article, we will study the singleton model. Let’s get started.

Introduction to the singleton pattern

First let’s take a look at the definition of a singleton pattern. The singleton pattern means that there is only one instance of a specific class, which is responsible for creating unique instances and providing a global access interface.

The UML class diagram for the singleton pattern can be represented as follows:

So why do we use the singleton pattern? Take a situation in your life. When you are crossing the street, is there only one traffic light that tells you whether you can cross the street or not? Because if there are two lights at the same time, you don’t know whether you should cross the street or not.

So the analogy to our software development, the same thing. In a system, there will be a unique instance of a purpose. This instance might be used to save some state in the application or perform some task. For example, in front-end development, we often use some application state management library, such as Vuex or Redux. In our application, there can only be one instance of managing state. If there are more than one instance, the state of the application will be problematic, leading to some errors in the application.

Implementation of singleton pattern

Let’s look at how the singleton pattern is implemented. From the UML class diagram above, we can see that for a class, we need a static variable to hold a reference to the instance, and we need to provide a static method to get the instance. If implemented using the ES6 class syntax, it can simply be represented by the following code:

class Singleton {
	// Static attributes of the class
	static instance = null;

	// Class static method
	static getInstance() {
		if (this.instance === null) {
			this.instance = new Singleton();
		}
		return this.instance; }}const a = Singleton.getInstance();
const b = Singleton.getInstance();

console.log(a === b); // true
Copy the code

The above code is relatively simple, I believe that you will know how to achieve a look. One thing to note is that in a static method of a class, this refers to the class, not the instance.

Let’s do it again with a function:

const Singleton = (function() {
	let instance;

	// A method to initialize a singleton object
	function initInstance() {
		return {};
	}

	return {
		getInstance() {
			if (instance === null) {
				instance = initInstance();
			}
			returninstance; }}; }) ();const a = Singleton.getInstance();
const b = Singleton.getInstance();

console.log(a === b);   // true
Copy the code

The implementation of both methods is similar, and you can choose different implementations according to your preference.

Singleton pattern in multithreaded environment

As Web front-end developers, because our development language is basically JavaScript, and because JavaScript is a single-threaded language, we generally don’t encounter some of the problems associated with using the singleton pattern in a multithreaded environment.

So what do we need to be aware of if we use the singleton pattern in a multi-threaded environment? First, if the code that created the singleton pattern is accessed by multiple threads while the singleton is not initialized, it is possible to create multiple singleton patterns without doing additional processing.

Of course, there are ways to solve this problem. One way is to generate the singleton when the class is initialized, so that the interface that fetches the singleton will fetch the same singleton that was generated in the first place. But you lose the benefit of the delayed initialization singleton. This is fine if the initialization of a singleton requires less resources or time. On the contrary, there is some waste to do so. This singleton may not be used once in the entire application.

Another way to create a singleton is to lock it so that only one thread can create it at a time. In this way we ensure that the singleton created is unique. Of course, the specific operation also depends on the language that implements the singleton pattern choice, which is not discussed in depth here.

Application scenarios and advantages of the singleton pattern

The singleton pattern is suitable for scenarios where a unique object is required to control, manage, and share the state of the system, or perform a specific task or implement a specific function. In our front-end development, the most common are the application’s state management objects, such as Vuex and Redux. Or a print log object, or a function plug-in, and so on. In short, the singleton pattern is relatively common in our daily development.

So what are the advantages of the singleton pattern? Here are a few:

  • There is only one instance globally, providing unified access and modification to ensure the consistency of status functions.
  • Simple, convenient and easy to implement.
  • Lazy initialization, where objects are initialized only when needed.

Disadvantages of the singleton pattern

While the singleton pattern has many advantages, it has many disadvantages, and some developers consider it anti-pattern. So when we use the singleton pattern, we must think carefully and determine whether it is necessary to use the singleton pattern. Because improper use of the singleton pattern will bring great difficulties to the testing, development and maintenance of the entire application. Let’s look at some of the drawbacks of the singleton pattern.

The abuse of singletons causes some of the same problems as global variables

For example, it increases the coupling of the code because the singleton pattern is globally accessible, and it is possible to use this unique object in many places, which leads to the coupling of the code.

Because the global state can be changed anywhere in the program that uses this singleton, you may have to troubleshoot in many places, which makes debugging and troubleshooting difficult.

The singleton pattern causes a lot of trouble for testing

Why is the singleton pattern a disaster for testing? If a singleton is used in your code, you need to initialize the singleton in advance when testing your code. This results in an inability to unit test the code when the singleton is not properly initialized.

And because the singleton pattern produces only one instance, it makes testing the same code multiple times problematic because the state of the instance may have changed during the last test, resulting in a failure or exception in the next test.

So the singleton pattern increases the difficulty and complexity of testing and increases the workload of testing code.

The singleton pattern violates the single responsibility principle of software design

This is a little bit easier to understand because, in general, a class is only responsible for what an instance of that class does; But for the singleton pattern, the singleton class is also responsible for producing only one instance. This violates the single responsibility principle of software design, in which a class is only responsible for the specific functionality of its instances, not the number of instances it produces.

But as to this disadvantage, people may have different opinions. The obvious thing is that it’s much more convenient and relatively simple to design and implement.

The singleton pattern hides the dependencies it requires

For ordinary classes, if our class depends on another class, we can usually express the dependent class explicitly through the constructor of the class. This way we know what dependencies the class needs when we initialize an instance of a specific class.

But for the singleton pattern, which encapsulates its dependencies internally, it is a black box for external consumers. The user does not know which dependencies are required to initialize the singleton, so it is easy to forget the dependencies required by the singleton when initializing the singleton, resulting in a singleton initialization failure.

Sometimes even when we know what dependencies are needed to initialize a singleton, they may be in order. It is also easy to import and use dependencies in the wrong order, causing problems with the initialization of singletons.

Summary of the singleton pattern

We already know from the above that singletons are a double-edged sword, so you need to be careful when using them. Considering the requirements of the scenario, is it necessary to use the singleton pattern to solve the current problem and is there any other solution? If singleton pattern must be used, how to standardize the use of singleton pattern, how to strike a balance between program development, maintainability, extensibility and simplicity of testing, is a problem worth considering.

This is the end of the article, if you have any questions and questions welcome to leave a comment below the article, or put forward here. You are also welcome to pay attention to my public account guanshan, which is not difficult to get more information about design patterns.

Here are some other articles in this series that you are also welcome to read, hoping that you will have a good grasp of the usage scenarios and solutions of these design patterns. If this article has helped you, please like it and share it

  • Design Mode Adventure Level 3: Factory mode encapsulates and decouples your code
  • Design Mode Adventure 2: Decorator mode, the home of the pancake
  • Design Mode Adventure Level 1: Observer mode

Reference links:

  • Use your singletons wisely
  • Singleton
  • Singleton Pattern Pitfalls
  • The singleton pattern
  • The singleton pattern