DEV Community

Cover image for Understanding the Singleton Pattern in TypeScript
bilel salem
bilel salem

Posted on

Understanding the Singleton Pattern in TypeScript

Hello everyone, السلام عليكم و رحمة الله و بركاته

Introduction

Design patterns are essential tools for solving common problems in software design. One of the most widely used patterns is the Singleton pattern. This pattern ensures that a class has only one instance and provides a global point of access to it. This article will deep dive into the Singleton pattern, explore its implementation in TypeScript, and provide real-world examples where this pattern proves to be highly beneficial, such as in Socket.IO connections and database connections.

What is the Singleton Pattern?

The Singleton pattern 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 Singleton pattern ensures that a class has only one instance and provides a global point of access to it.

Implementing Singleton in TypeScript

To implement a Singleton in TypeScript, you need to follow these steps:

  1. Private Constructor: Ensure that the class cannot be instantiated from outside the class.
  2. Static Method: Provide a static method that returns the instance of the class.
  3. Private Static Variable: Hold the single instance of the class.

Here’s a basic implementation:

class Singleton {
    private static instance: Singleton;

    private constructor() {
        // private constructor to prevent direct instantiation
    }

    public static getInstance(): Singleton {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }

    public someBusinessLogic() {
        // business logic here
    }
}

// Usage
const singletonInstance = Singleton.getInstance();
singletonInstance.someBusinessLogic();
Enter fullscreen mode Exit fullscreen mode

Real-World Examples

Now, let's look at how the Singleton pattern can be applied in real-world scenarios.

1. Singleton Pattern with Socket.IO

Socket.IO is a library that enables real-time, bidirectional, and event-based communication. When dealing with sockets, it's often crucial to maintain a single connection instance throughout the application to ensure consistent and efficient communication.

Here's how you can implement a Singleton for a Socket.IO connection in TypeScript:

import { io, Socket } from 'socket.io-client';

class SocketSingleton {
    private static instance: Socket;
    private constructor() {}

    public static getInstance(): Socket {
        if (!SocketSingleton.instance) {
            SocketSingleton.instance = io('http://localhost:3000');
        }
        return SocketSingleton.instance;
    }
}

// Usage
const socket = SocketSingleton.getInstance();
socket.emit('event', { data: 'some data' });
socket.on('response', (data) => {
    console.log(data);
});
Enter fullscreen mode Exit fullscreen mode
2. Singleton Pattern with Database Connections

Managing database connections efficiently is crucial in any application to avoid the overhead of creating multiple connections and to manage resources properly. Using the Singleton pattern for database connections ensures that only one connection instance is used throughout the application.

Here's an example using a hypothetical database client:

import { DatabaseClient } from 'some-database-client';

class DatabaseConnection {
    private static instance: DatabaseClient;

    private constructor() {}

    public static getInstance(): DatabaseClient {
        if (!DatabaseConnection.instance) {
            DatabaseConnection.instance = new DatabaseClient({
                host: 'localhost',
                user: 'root',
                password: 'password',
                database: 'my_db'
            });
        }
        return DatabaseConnection.instance;
    }
}

// Usage
const db = DatabaseConnection.getInstance();
db.query('SELECT * FROM users', (err, results) => {
    if (err) throw err;
    console.log(results);
});
Enter fullscreen mode Exit fullscreen mode

What Happens if We Don't Use the Singleton Pattern?

Socket.IO Connections

Without the Singleton pattern, multiple instances of Socket.IO connections could be created. This can lead to:

  • Inconsistent Communication: Each instance would manage its own connection, resulting in inconsistent state and data.
  • Increased Resource Usage: Multiple connections consume more memory and CPU resources, leading to inefficient resource management.
  • Event Duplication: Events may be emitted and received multiple times, causing unexpected behavior and bugs.
Database Connections

Without the Singleton pattern for database connections, the following issues might arise:

  • Connection Overhead: Each request might open a new database connection, leading to a high overhead in managing these connections.
  • Resource Exhaustion: Databases have a limited number of connections they can handle simultaneously. Multiple instances can quickly exhaust available connections.
  • Inconsistent State: Different parts of the application might work with different instances, leading to inconsistencies and potential data integrity issues.

Advantages of Using Singleton Pattern

  • Controlled Access: It provides controlled access to the single instance.
  • Reduced Namespace Pollution: It avoids the need for global variables, reducing namespace pollution.
  • Lazy Initialization: The instance is created only when it's needed, optimizing resource use.
  • Consistency: Ensures that a class has only one instance, providing consistent data across the application.

Conclusion

The Singleton pattern is a powerful tool in the software design, especially useful for managing resources such as database connections and socket communications.
It's implementation in TypeScript is straightforward, and its application in real-world scenarios like Socket.IO connections and database management demonstrates its practical utility. By understanding and utilizing the Singleton pattern, you can avoid the pitfalls associated with multiple instances.

Top comments (2)

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

Have you ever thought about single-ness via design (like a dependency injection container singleton) rather than actual single-ness that is enforced?

I Remember back in the day, creating a file, to make sure something was "single" at the machine level, rather than single process.

I Also think that was an extreme waste of engineering effort and talent on solving meaningless problems.

Collapse
 
bilelsalemdev profile image
bilel salem

Your observation about the potential waste of engineering effort on solving meaningless problems resonates with the idea that sometimes, developers may over-engineer solutions or focus on optimizing aspects that don't significantly impact the overall functionality or performance of the software. It's important in software development to balance optimization with practicality and focus efforts on solving real problems that enhance the user experience or address critical business needs.