阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 单例模式(Singleton)的多种实现方式

单例模式(Singleton)的多种实现方式

作者:陈川 阅读数:25354人阅读 分类: JavaScript

单例模式是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。在JavaScript中,单例的实现方式多样,每种方式各有优缺点,适用于不同场景。

最简单的单例实现

最简单的单例实现方式是直接使用对象字面量。由于JavaScript的对象字面量本身就是单例,无需额外处理:

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

这种方式简单直接,但缺乏私有属性和方法,也无法延迟初始化。

使用闭包实现单例

闭包可以创建私有变量,实现更完整的单例模式:

const Singleton = (function() {
  let instance;
  
  function init() {
    // 私有方法和属性
    const privateVariable = 'private';
    function privateMethod() {
      console.log('I am private');
    }
    
    return {
      // 公有方法和属性
      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

这种方式实现了延迟初始化和私有成员,是经典的单例实现。

ES6 Class实现单例

使用ES6的class语法可以更清晰地实现单例:

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
      // 初始化代码
      this.property = 'value';
    }
    return Singleton.instance;
  }
  
  method() {
    console.log('Singleton method');
  }
}

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

这种实现利用了类的静态属性存储实例,构造函数中检查并返回唯一实例。

使用模块系统实现单例

在现代JavaScript中,模块系统天然支持单例模式:

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

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

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

// 使用
import singleton from './singleton.js';

模块在首次导入时执行一次,后续导入会返回相同的导出对象,实现了单例。

惰性单例模式

惰性单例在需要时才创建实例,适用于资源密集型对象:

const LazySingleton = (function() {
  let instance = null;
  
  function createInstance() {
    const object = new Object('I am the instance');
    // 初始化操作
    return object;
  }
  
  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// 使用时才创建实例
const instance = LazySingleton.getInstance();

线程安全的单例实现

虽然JavaScript是单线程的,但在某些环境如Node.js中,模块加载需要考虑线程安全:

class ThreadSafeSingleton {
  constructor() {
    if (typeof ThreadSafeSingleton.instance === 'object') {
      return ThreadSafeSingleton.instance;
    }
    
    // 加锁模拟
    this.lock = false;
    if (!this.lock) {
      this.lock = true;
      // 初始化
      this.property = 'initialized';
      ThreadSafeSingleton.instance = this;
      this.lock = false;
    }
    
    return ThreadSafeSingleton.instance;
  }
}

单例模式的变体

有时需要限制实例数量而非仅一个,这是单例模式的变体:

function LimitedInstances(maxInstances = 1) {
  const instances = [];
  
  return class {
    constructor() {
      if (instances.length >= maxInstances) {
        return instances[0]; // 或抛出错误
      }
      instances.push(this);
    }
  };
}

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

单例注册表模式

当需要管理多种类型的单例时,可以使用注册表模式:

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

单例模式的现代实现

使用Proxy可以创建更灵活的单例:

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

单例模式与依赖注入

在大型应用中,单例可以通过依赖注入容器管理:

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;
  }
}

// 注册服务
Container.register('logger', () => {
  return {
    log: console.log
  };
});

// 获取单例
const logger1 = Container.get('logger');
const logger2 = Container.get('logger');
console.log(logger1 === logger2); // true

单例模式的测试考虑

测试单例时需要特别注意,因为单例状态会在测试间共享:

// 测试前重置单例
class ResetableSingleton {
  static instance;
  static reset() {
    this.instance = null;
  }
  
  constructor() {
    if (ResetableSingleton.instance) {
      return ResetableSingleton.instance;
    }
    // 初始化
    this.value = 0;
    ResetableSingleton.instance = this;
  }
}

// 测试用例
beforeEach(() => {
  ResetableSingleton.reset();
});

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

单例模式在框架中的应用

许多前端框架如Vue、React中都有单例模式的应用:

// Vue中的单例模式示例
const store = {
  state: {
    count: 0
  },
  increment() {
    this.state.count++;
  }
};

// 在组件中使用
Vue.component('counter', {
  template: '<button @click="increment">{{ count }}</button>',
  computed: {
    count() {
      return store.state.count;
    }
  },
  methods: {
    increment() {
      store.increment();
    }
  }
});

单例模式的替代方案

有时依赖注入或上下文API比单例更合适:

// React Context作为单例替代
const SingletonContext = React.createContext();

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

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

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌