Singleton replacement. Architecture

Singleton Replacement in TypeScript Architecture

Singleton pattern is a widely used design pattern in software development. It ensures that only one instance of a class is created and provides a global point of access to it. However, in TypeScript, the singleton pattern can be replaced with more modern and flexible approaches. In this article, we will explore some alternatives to the traditional singleton pattern in TypeScript architecture.

1. Dependency Injection

Dependency Injection (DI) is a powerful technique that promotes loose coupling and modular design. Instead of relying on a single global instance, DI allows us to inject dependencies into classes at runtime. This eliminates the need for a singleton and makes the code more testable and maintainable.

Here’s an example of how DI can be used to replace a singleton:


class Logger {
  log(message: string) {
    console.log(message);
  }
}

class UserService {
  private logger: Logger;

  constructor(logger: Logger) {
    this.logger = logger;
  }

  getUser(id: number) {
    this.logger.log(`Fetching user with id ${id}`);
    // ...
  }
}

const logger = new Logger();
const userService = new UserService(logger);
userService.getUser(123);

In this example, the Logger instance is injected into the UserService constructor. This allows us to easily replace the logger with a different implementation or mock it for testing purposes.

2. Factory Pattern

The Factory pattern is another alternative to the singleton pattern. It provides a centralized way of creating instances of a class without exposing the creation logic. This allows us to have multiple instances of a class while still maintaining control over their creation.

Here’s an example of how the Factory pattern can be used:


class DatabaseConnection {
  // ...
}

class DatabaseConnectionFactory {
  createConnection(): DatabaseConnection {
    // Create and configure the connection
    // ...

    return new DatabaseConnection();
  }
}

const connectionFactory = new DatabaseConnectionFactory();
const connection1 = connectionFactory.createConnection();
const connection2 = connectionFactory.createConnection();

In this example, the DatabaseConnectionFactory is responsible for creating instances of DatabaseConnection. This allows us to have multiple connections without relying on a singleton.

3. Module-Level State

In some cases, you may need to maintain a global state in your application. Instead of using a singleton, you can leverage the module system in TypeScript to achieve this.

Here’s an example:


// logger.ts
export function log(message: string) {
  console.log(message);
}

// user-service.ts
import { log } from './logger';

export function getUser(id: number) {
  log(`Fetching user with id ${id}`);
  // ...
}

// app.ts
import { getUser } from './user-service';

getUser(123);

In this example, the log function is exported from the logger.ts module and used in the getUser function in the user-service.ts module. This allows us to maintain a global state (in this case, the logger) without relying on a singleton.

These are just a few alternatives to the traditional singleton pattern in TypeScript architecture. Depending on your specific requirements, you may choose one or a combination of these approaches to achieve a more flexible and maintainable codebase.

Let us know in the comments which approach you prefer and if you have any other suggestions for replacing singletons in TypeScript architecture.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *