阿里云主机折上折
  • 微信号
Current Site:Index > Multiple implementation approaches of the Singleton pattern

Multiple implementation approaches of the Singleton pattern

Author:Chuan Chen 阅读数:25353人阅读 分类: JavaScript

The Singleton pattern is a commonly used design pattern that ensures a class has only one instance and provides a global access point to it. In JavaScript, there are various ways to implement the Singleton pattern, each with its own advantages and disadvantages, making them suitable for different scenarios.

Simplest Singleton Implementation

The simplest way to implement a Singleton is by using an object literal. Since JavaScript object literals are inherently singletons, no additional processing is required:

const singleton = {
  property: 'value',
  method() {
    console.log('I am a singleton');
  }
};

This approach is straightforward but lacks private properties and methods and does not support lazy initialization.

Implementing Singleton Using Closures

Closures can be used to create private variables, enabling a more complete Singleton implementation:

const Singleton = (function() {
  let instance;
  
  function init() {
    // Private methods and properties
    const privateVariable = 'private';
    function privateMethod() {
      console.log('I am private');
    }
    
    return {
      // Public methods and properties
      publicMethod() {
        console.log('public can see me');
      },
      publicProperty: 'public'
    };
  }
  
  return {
    getInstance() {
      if (!instance) {
        instance = init();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

This approach achieves lazy initialization and private members, making it a classic Singleton implementation.

Singleton Implementation Using ES6 Class

The ES6 class syntax allows for a clearer Singleton implementation:

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
      // Initialization code
      this.property = 'value';
    }
    return Singleton.instance;
  }
  
  method() {
    console.log('Singleton method');
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true

This implementation uses a static property to store the instance and checks for it in the constructor, returning the unique instance.

Singleton Implementation Using Module System

In modern JavaScript, the module system natively supports the Singleton pattern:

// singleton.js
let instance;
let privateVar = 'private';

function privateMethod() {
  console.log('private method');
}

export default {
  publicMethod() {
    console.log('public method');
  },
  publicVar: 'public'
};

// Usage
import singleton from './singleton.js';

Modules are executed only once upon first import, and subsequent imports return the same exported object, effectively implementing a Singleton.

Lazy Singleton Pattern

A lazy Singleton creates the instance only when needed, which is useful for resource-intensive objects:

const LazySingleton = (function() {
  let instance = null;
  
  function createInstance() {
    const object = new Object('I am the instance');
    // Initialization operations
    return object;
  }
  
  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// Instance is created only when used
const instance = LazySingleton.getInstance();

Thread-Safe Singleton Implementation

Although JavaScript is single-threaded, in environments like Node.js, module loading requires thread safety considerations:

class ThreadSafeSingleton {
  constructor() {
    if (typeof ThreadSafeSingleton.instance === 'object') {
      return ThreadSafeSingleton.instance;
    }
    
    // Simulate locking
    this.lock = false;
    if (!this.lock) {
      this.lock = true;
      // Initialization
      this.property = 'initialized';
      ThreadSafeSingleton.instance = this;
      this.lock = false;
    }
    
    return ThreadSafeSingleton.instance;
  }
}

Variations of the Singleton Pattern

Sometimes, it's necessary to limit the number of instances rather than allowing only one, which is a variation of the Singleton pattern:

function LimitedInstances(maxInstances = 1) {
  const instances = [];
  
  return class {
    constructor() {
      if (instances.length >= maxInstances) {
        return instances[0]; // Or throw an error
      }
      instances.push(this);
    }
  };
}

const SingleInstance = LimitedInstances(1);
const instance1 = new SingleInstance();
const instance2 = new SingleInstance();
console.log(instance1 === instance2); // true

Singleton Registry Pattern

When managing multiple types of Singletons, the registry pattern can be used:

class SingletonRegistry {
  static instances = new Map();
  
  static getInstance(className, ...args) {
    if (!this.instances.has(className)) {
      this.instances.set(className, new className(...args));
    }
    return this.instances.get(className);
  }
}

class Logger {
  constructor() {
    this.logs = [];
  }
  
  log(message) {
    this.logs.push(message);
    console.log(message);
  }
}

const logger1 = SingletonRegistry.getInstance(Logger);
const logger2 = SingletonRegistry.getInstance(Logger);
console.log(logger1 === logger2); // true

Modern Singleton Implementation

Using Proxy allows for more flexible Singleton creation:

function singleton(className) {
  let instance;
  return new Proxy(className, {
    construct(target, args) {
      if (!instance) {
        instance = new target(...args);
      }
      return instance;
    }
  });
}

class Database {
  constructor() {
    this.connection = 'connected';
  }
}

const SingleDatabase = singleton(Database);
const db1 = new SingleDatabase();
const db2 = new SingleDatabase();
console.log(db1 === db2); // true

Singleton Pattern and Dependency Injection

In large applications, Singletons can be managed through a dependency injection container:

class Container {
  static services = {};
  
  static register(name, creator) {
    this.services[name] = { creator, instance: null };
  }
  
  static get(name) {
    const service = this.services[name];
    if (!service.instance) {
      service.instance = service.creator();
    }
    return service.instance;
  }
}

// Register a service
Container.register('logger', () => {
  return {
    log: console.log
  };
});

// Get the Singleton
const logger1 = Container.get('logger');
const logger2 = Container.get('logger');
console.log(logger1 === logger2); // true

Testing Considerations for Singleton Pattern

Testing Singletons requires special attention because their state is shared across tests:

// Reset Singleton before testing
class ResetableSingleton {
  static instance;
  static reset() {
    this.instance = null;
  }
  
  constructor() {
    if (ResetableSingleton.instance) {
      return ResetableSingleton.instance;
    }
    // Initialization
    this.value = 0;
    ResetableSingleton.instance = this;
  }
}

// Test case
beforeEach(() => {
  ResetableSingleton.reset();
});

test('singleton test', () => {
  const instance1 = new ResetableSingleton();
  instance1.value = 42;
  const instance2 = new ResetableSingleton();
  expect(instance2.value).toBe(42);
});

Singleton Pattern in Frameworks

Many frontend frameworks like Vue and React utilize the Singleton pattern:

// Singleton example in Vue
const store = {
  state: {
    count: 0
  },
  increment() {
    this.state.count++;
  }
};

// Usage in components
Vue.component('counter', {
  template: '<button @click="increment">{{ count }}</button>',
  computed: {
    count() {
      return store.state.count;
    }
  },
  methods: {
    increment() {
      store.increment();
    }
  }
});

Alternatives to the Singleton Pattern

Sometimes dependency injection or context APIs are more suitable than Singletons:

// React Context as a Singleton alternative
const SingletonContext = React.createContext();

function App() {
  const singletonValue = { key: 'value' };
  
  return (
    <SingletonContext.Provider value={singletonValue}>
      <ChildComponent />
    </SingletonContext.Provider>
  );
}

function ChildComponent() {
  const context = useContext(SingletonContext);
  // Use context
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.