Implement the Singleton pattern
Tags: webdev, javascript, tutorial, learning
Published: 2024-02-19 06:14:42

The Singleton pattern is just one of many design patterns in software development. Design patterns are like reusable templates that solve common problems in software design. They help us create code that's easy to maintain, scale, and make robust.

There are three main types of design patterns: creational, structural, and behavioral. **Creational patterns** help us create objects in specific situations. **Structural patterns** focus on how classes and objects are put together to form larger structures. **Behavioral patterns** are all about how objects interact with each other and share responsibilities.

The **Singleton pattern** is a creational pattern because it helps us create objects. It ensures that only one instance of a class is created and that the instance is globally accessible throughout the application.

In this post, we'll dive deeper into the Singleton pattern and learn how to use JavaScript Proxy to implement it.

## Demystifying the Singleton design pattern

To better understand the Singleton pattern, let's take an example. Suppose we have a class called `DatabaseConnection` that connects to a database and exposes some methods to execute queries.

We want to ensure that only one instance of this class is created throughout the application because creating multiple instances would be expensive and unnecessary. That's where the Singleton pattern comes in handy.

Here's how we can implement the `DatabaseConnection` class using the Singleton pattern:

```js
class Database {
    query(sql) {
        // ...
    }
}

const DatabaseConnection = (() => {
    let instance;

    return {
        getInstance: () => {
            if (!instance) {
                instance = new Database();
            }
            return instance;
        },
    };
})();
```

In this example, we're using an IIFE (**Immediately Invoked Function Expression**) to create a closure that encapsulates the `DatabaseConnection` class. We define a private variable called `instance` that holds the single instance of the class.

The reason we use an IIFE to create the `DatabaseConnection` class is to keep it private and prevent any potential naming collisions or interference with other parts of our application.

The public method `getInstance()` checks if an instance of the class already exists. If it does, it returns that instance. Otherwise, it creates a new instance by calling `new Database()` and sets it as the value of `instance`.

To use the Singleton, simply call its public method `getInstance()`.

```js
const connection1 = DatabaseConnection.getInstance();
const connection2 = DatabaseConnection.getInstance();

console.log(connection1 === connection2);   // true
```

The beauty of the Singleton is that it ensures there is only one instance of the class in our application. We can see from our usage example above that both variables hold the same instance of the class because they are strictly equal (`===`).

## Using Proxy to implement the Singleton pattern

Another way to implement the Singleton pattern in JavaScript is by using Proxy to modify the class constructor. With this approach, we can trap the constructor of the `DatabaseConnection` class using a Proxy object.

Here's how we can modify our previous implementation to use Proxy:

```js
class Database {
    // ...
}

const DatabaseConnection = (() => {
    let instance;

    // Create a proxy for the class constructor
    const handler = {
        construct(target, args) {
            if (!instance) {
                instance = new Database();
            }
            return instance;
        },
    };

    return new Proxy(function() {}, handler);
})();
```

In this example, we're creating a new `Proxy` object that uses a special method called `construct` to intercept any attempts to create a new instance of our class using the `new` keyword.

Inside the `construct` method, we first check if an instance of the class already exists. If it does, we simply return that instance. Otherwise, we create a new instance of the class by calling `new Database()` and set it as the value of `instance`.

Finally, we replace our original variable declaration with a call to the empty constructor function wrapped in our proxy object. This ensures that all attempts to create a new object of type `DatabaseConnection` are intercepted by our proxy handler.

By using this approach, we can guarantee that our singleton class will only ever have one instance. Any attempts to create additional instances will simply return a reference to the existing instance.

```js
const connection1 = new DatabaseConnection();
const connection2 = new DatabaseConnection();

console.log(connection1 === connection2);   // true
```

## Creating Singleton instances for any class

We know how to implement the Singleton pattern for a specific class, but what if we want to create singletons for multiple classes? It's not practical to write similar code for each class. Luckily, we can create a generic function that returns a singleton instance of any class.

Here's how we can achieve this:

```js
const createSingleton = (Class) => {
    let instance;

    return new Proxy(Class, {
        construct(target, args) {
            if (!instance) {
                instance = new target(...args);
            }
            return instance;
        },
    });
};
```

In this example, we have a function called `createSingleton()` that takes a class as an argument and returns a singleton instance of that class.

The function creates a private variable called `instance` and uses `Proxy` to trap the `construct` method. Inside the `construct` method, we check if an instance of the class already exists. If it does not exist, we create a new instance by calling `new target(...args)` (where `target` refers to the input argument `Class`) and set it as the value of `instance`.

Finally, we return our proxy object with the trapped constructor. With this function, we can create singletons for any class.

For example, let's say we have a class called `Logger`. We can use `createSingleton(Logger)` to create a singleton instance of `Logger`.

```js
class Logger {
    log(message) {
        console.log(`[Logger] ${message}`);
    }
}

const SingletonLogger = createSingleton(Logger);
```

Now, we can create two objects from this singleton and confirm that both variables are referencing the same object by checking their strict equality using `===`.

```js
const logger1 = new SingletonLogger();
const logger2 = new SingletonLogger();

console.log(logger1 === logger2);   // true
```

## Conclusion

In this post, we talked about design patterns, specifically the Singleton pattern. We learned how to use JavaScript Proxy to implement the Singleton pattern and modify the class constructor to create a single instance of any class.

Using the Singleton pattern can help optimize performance and memory usage by ensuring that only one instance of a class is created throughout the application. This is especially helpful for classes that are expensive to create or have shared state.

However, it's important to use the Singleton pattern wisely. Overusing singletons can lead to tight coupling between modules, making code harder to test and maintain. Before deciding to use this pattern, it's important to weigh the benefits against the potential drawbacks.

---

If you found this series helpful, please consider giving the [repository](https://github.com/phuocng/javascript-proxy) a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)