COMP 305: Object-Oriented Software Design

University of San Diego, Fall 2025

Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance. It's useful for managing shared resources like database connections, configuration settings, or logging services.

Key Characteristics

Interactive Demo

Try creating multiple instances and see how they all reference the same object:

Output

// Click buttons to interact with the Singleton
Instances Created: 0
Total Logs: 0

Implementation Examples

Basic Singleton

// Basic Singleton Pattern
class DatabaseConnection {
    constructor() {
        if (DatabaseConnection.instance) {
            return DatabaseConnection.instance;
        }

        this.connected = false;
        this.queries = [];
        DatabaseConnection.instance = this;
    }

    connect() {
        if (!this.connected) {
            this.connected = true;
            console.log('Database connected');
        }
    }

    query(sql) {
        if (this.connected) {
            this.queries.push(sql);
            console.log(`Executing: ${sql}`);
        }
    }

    getQueryCount() {
        return this.queries.length;
    }
}

// Usage
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();

console.log(db1 === db2); // true - same instance!

Modern Singleton with Configuration

// Modern Singleton with Module Pattern
class ConfigurationManager {
    constructor() {
        if (ConfigurationManager.instance) {
            return ConfigurationManager.instance;
        }

        this.config = {
            apiUrl: 'https://api.example.com',
            timeout: 5000,
            retries: 3
        };

        ConfigurationManager.instance = this;
    }

    get(key) {
        return this.config[key];
    }

    set(key, value) {
        this.config[key] = value;
    }

    getAll() {
        return { ...this.config };
    }
}

// ES6 Module Singleton (even simpler)
export const config = new ConfigurationManager();

Lazy Initialization with Static Method

// Lazy Initialization Singleton
class Logger {
    static #instance = null;

    constructor() {
        if (Logger.#instance) {
            throw new Error('Use Logger.getInstance()');
        }

        this.logs = [];
    }

    static getInstance() {
        if (!Logger.#instance) {
            Logger.#instance = new Logger();
        }
        return Logger.#instance;
    }

    log(message) {
        const entry = {
            timestamp: new Date().toISOString(),
            message
        };
        this.logs.push(entry);
        console.log(`[${entry.timestamp}] ${message}`);
    }

    getLogs() {
        return this.logs;
    }

    clear() {
        this.logs = [];
    }
}

// Usage
const logger = Logger.getInstance();
logger.log('Application started');

Common Use Cases

Benefits

Trade-offs and Concerns

Alternatives to Consider