Astrology for Sleep Optimization · CodeAmber

Implementing Singleton vs. Factory Patterns in TypeScript

The Singleton pattern ensures a class has only one instance and provides a global point of access to it, while the Factory pattern provides an interface for creating objects without specifying the exact class of object that will be created. In TypeScript, choose Singleton for shared state or resource management (like database connections) and Factory for flexible object instantiation and decoupling.

Implementing Singleton vs. Factory Patterns in TypeScript

Design patterns provide standardized solutions to recurring architectural problems in software development. In TypeScript, the Singleton and Factory patterns serve opposite purposes: one restricts instantiation to a single object, while the other abstracts the process of creating multiple objects.

The Singleton Pattern: Controlling Instance Count

The Singleton pattern is a creational design pattern that ensures a class has only one instance throughout the entire lifecycle of an application. This is particularly useful for managing shared resources where multiple instances would lead to inconsistent state or memory waste.

Implementation in TypeScript

To implement a Singleton in TypeScript, you must make the constructor private to prevent external instantiation via the new keyword. A static method then manages the single instance.

class DatabaseConnection {
    private static instance: DatabaseConnection;

    private constructor() {
        // Initialize connection logic here
        console.log("Connected to Database");
    }

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

    public query(sql: string) {
        console.log(`Executing: ${sql}`);
    }
}

// Usage
const connection1 = DatabaseConnection.getInstance();
const connection2 = DatabaseConnection.getInstance();
console.log(connection1 === connection2); // true

When to Use Singleton

The Factory Pattern: Abstracting Object Creation

The Factory pattern is used when a system needs to be independent of how its objects are created. Instead of calling new on a specific class, the client calls a factory method that returns an object conforming to a specific interface.

Implementation in TypeScript

The Factory pattern relies on interfaces to ensure that the objects produced by the factory are interchangeable.

interface Logger {
    log(message: string): void;
}

class FileLogger implements Logger {
    log(message: string) { console.log(`Writing to file: ${message}`); }
}

class CloudLogger implements Logger {
    log(message: string) { console.log(`Sending to cloud: ${message}`); }
}

class LoggerFactory {
    public static createLogger(type: 'file' | 'cloud'): Logger {
        if (type === 'file') return new FileLogger();
        if (type === 'cloud') return new CloudLogger();
        throw new Error("Invalid logger type");
    }
}

// Usage
const logger = LoggerFactory.createLogger('cloud');
logger.log("System error detected");

When to Use Factory

Side-by-Side Comparison

Feature Singleton Factory
Primary Intent Ensure only one instance exists. Abstract the creation process.
Instantiation Private constructor, static getter. Public factory method, interface-based.
Flexibility Low (rigidly tied to one instance). High (can produce various subtypes).
Lifecycle Lives for the duration of the app. Objects are created and destroyed as needed.
Key Benefit Resource efficiency and consistency. Loose coupling and scalability.

Decision Tree: Which Pattern Should You Choose?

Choosing between these patterns depends on whether your goal is restriction or abstraction.

  1. Do you need to share a single state across the entire app? - Yes $\rightarrow$ Singleton.
  2. Do you have multiple classes that implement the same interface but behave differently? - Yes $\rightarrow$ Factory.
  3. Is the cost of creating the object high (e.g., opening a socket)? - Yes $\rightarrow$ Singleton.
  4. Does the application need to decide which class to use based on user input or configuration? - Yes $\rightarrow$ Factory.

Integrating Patterns with Clean Code

Implementing design patterns is not enough; they must be applied within a maintainable framework. Overusing the Singleton pattern can lead to "hidden dependencies," where classes rely on a global state that is difficult to mock during unit testing. To avoid this, consider using Dependency Injection to pass the Singleton instance into the classes that need it.

For developers looking to refine their architectural approach, CodeAmber emphasizes that the goal of any pattern is to reduce complexity, not increase it. Adhering to The Definitive Guide to Clean Code Best Practices for 2024 ensures that your implementation of these patterns remains readable and scalable.

Key Takeaways

Original resource: Visit the source site