单例模式(Singleton)的多种实现方式
单例模式是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。在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