单例模式
保证一个类仅仅只有一个实例对象,并且提供一个对该实例对象的全局访问点。
如:浏览器中的window对象
//示例=================全局配置管理=================
//一、Class + static instance(经典单例模式)
/**
* 使用 private constructor 私有化构造函数,禁止外部通过new 类名()的方式创建实例,只能在类的内部使用new
* 使用 static instance 保证全局唯一实例
* 通过 getInstance 统一管理实例生命周期
*/
class AppConfig{
// 保存唯一实例(类级别) AppConfig 表示这个类本身 | null 表示这个实例可以为空
private static instance:AppConfig | null = null;
// 对外只读配置
public readonly apiUrl:string;
public readonly apiKey:string;
// 私有构造函数:禁止外部通过 new 创建实例
private constructor(){
this.apiUrl = getConfig("apiUrl");
this.apiKey = getConfig("apiKey");
}
// 唯一实例获取入口 类名.getInstance()
public static getInstance():AppConfig{
if(!AppConfig.instance){//访问类的静态属性(类本身)
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
}
// 使用方式
const configClass = AppConfig.getInstance();
// 二、闭包 + Object.freeze(函数式单例)
/**
* 使用闭包在函数作用域内维护唯一实例
* 通过 Object.freeze 保证实例不可变
*/
function getAppConfig(){
// 闭包私有变量,外部无法访问
let instance:{
readonly apiUrl:string;
readonly apiKey:string;
} | null = null;
// 返回获取单例的函数
return function(){
if(!instance){
instance = Object.freeze({
apiUrl:getConfig("apiUrl"),
apiKey:getConfig("apiKey")
});
}
return instance;
}
}
const getConfigFn = getAppConfig();// 初始化一次,得到“获取实例的函数”
const configFn = getConfigFn();// 获取单例对象,每次调用都返回同一个实例
//三、ES Module 本身就是单例作用域
/**
* ES Module 在运行时只会被初始化一次
* 通过 Object.freeze 保证实例不可变
* 多次 import 获取的是同一个实例引用
*/
export const config = Object.freeze({
apiUrl:getConfig("apiUrl"),
apiKey:getConfig("apiKey")
});
/**
* 应用场景选型:
* - 仅用于配置 / 常量:优先 ES Module 单例
* - 需要对象语义或扩展能力:使用 Class 单例
* - 偏函数式风格:使用闭包单例
*/
//示例=================事件总线================= EventBus 是无副作用的纯逻辑模块,适合在模块加载时通过 IIFE 创建全局唯一实例
type EventHandler<T = any> = (payload:T) => void;
export const eventBus = (() => {
// 私有监听器表,外部无法直接访问
const listeners: Record<string, EventHandler[]> = {};
return {
// 注册事件
on<T = any>(event: string, handler: EventHandler<T>) {
if (!listeners[event]) {
listeners[event] = [];
}
listeners[event].push(handler);
},
// 取消监听(防止组件卸载后内存泄漏)
off<T = any>(event: string, handler: EventHandler<T>) {
if (!listeners[event]) return;
listeners[event] = listeners[event].filter(fn => fn !== handler);
},
// 触发事件
emit<T = any>(event: string, payload?: T) {
listeners[event]?.forEach(handler => handler(payload));
}
};
})();//在模块加载时立即执行一次,整个应用生命周期中只会存在一个 EventBus 实例。
//示例=================webSocket连接管理================= WebSocket 属于高副作用资源,涉及网络连接和生命周期管理,不适合立即执行。
/**
* 使用单例模式管理 webSocket 连接 WebSocket 属于有副作用、需要参数、需要可控生命周期的资源,不适合在模块加载时立即执行。
* 通过 eventBus 实现消息的统一分发
* 使用闭包维护连接状态和事件处理
*/
type WSStatus = 'INIT' | 'CONNECTING' | 'OPEN' | 'CLOSED';
export function createWebSocketSingleton(url: string) {
let ws: WebSocket | null = null;
let status: WSStatus = 'INIT';
function connect() {
if (ws && (status === 'CONNECTING' || status === 'OPEN')) {
return ws;
}
status = 'CONNECTING';
ws = new WebSocket(url);
ws.onopen = () => {
status = 'OPEN';
console.log('[WebSocket] connected');
eventBus.emit('ws:open');
};
ws.onmessage = (event) => {
// 所有消息统一通过 eventBus 分发
eventBus.emit('ws:message', event.data);
};
ws.onerror = (error) => {
console.error('[WebSocket] error', error);
eventBus.emit('ws:error', error);
};
ws.onclose = () => {
status = 'CLOSED';
console.log('[WebSocket] closed');
eventBus.emit('ws:close');
ws = null;
};
return ws;
}
function send(data: string) {
if (ws && status === 'OPEN') {
ws.send(data);
} else {
console.warn('[WebSocket] not connected');
}
}
function close() {
ws?.close();
ws = null;
status = 'CLOSED';
}
return {
connect,
send,
close,
getStatus: () => status
};
}
简单工厂 / 工厂方法 / 抽象工厂模式
简单工厂模式
- 工厂封装对象创建逻辑,使客户端依赖抽象而非具体实现,从而解耦对象的使用与创建。
- 产品是工厂方法返回的实例,其具体实现对客户端透明,客户端仅依赖其抽象契约。
//示例=================简单工厂模式=================
//产品接口
interface Product{
name:string
use():void
}
//创建产品工厂
const makeProductFactory = (prefix:string) =>(type:"A"|"B"):Product=>
({
name:`${prefix}-${type}`,
use(){
console.log(`${this.name} is used`)
}
})
//使用工厂创建产品
const factory = makeProductFactory("product")
const productA = factory("A")
const productB = factory("B")
productA.use()
productB.use()
缺点:
● 单个工厂方法需要判断 type 并创建不同产品
● 每次新增产品类型,都必须修改工厂函数(修改逻辑/添加分支)
● 违反开闭原则(对修改开放,对扩展封闭)
工厂方法模式
改进:
- 每个产品对应一个工厂
- 工厂类(函数)只负责创建自己的产品
- 客户端通过工厂接口(抽象工厂)获取产品
- 新增产品无需修改现有工厂,只需要增加新的工厂类/函数
//示例=================工厂方法模式=================
//产品接口
interface Product{
name:string
use():void
}
//创建产品A
const createProductA():Product=>({
name:"ProductA",
use(){
console.log(`${this.name} is used`)
}
})
//创建产品B
function createProductB():Product{
return{
name:"ProductB",
use(){
console.log(`${this.name} is used`)
}
}
}
//创建工厂接口
/**
* Factory 接口约定:
* - 客户端只能通过 createProduct() 获取 Product
* - 客户端不关心具体 Product 的类型和创建方式
*
* 思考:
* 为什么不直接使用 createProductA() / createProductB() 来创建产品,
* 而是通过 Factory 接口暴露统一的 createProduct() 方法。
*
* 原因:
* 该模式的意义不在于“如何创建对象”,
* 而在于“冻结客户端获取 Product 的创建协议(creation contract)”。
*
* Factory 接口冻结了:
* - 客户端「如何获得一个 Product」的方式
* - 而不是某一个具体产品的创建实现
*
* 通过这层抽象:
* - 客户端不再依赖具体的产品类型
* - 新增产品只需新增对应的 Factory 实现
* - 无需修改现有客户端逻辑,符合开闭、依赖倒置原则
*/
interface Factory{
createProduct():Product
}
const factoryA: Factory = {
createProduct: createProductA
};
const factoryB: Factory = {
createProduct: createProductB
};
//使用工厂创建产品
const productA = factoryA.createProduct()
const productB = factoryB.createProduct()
productA.use()
productB.use()
缺点:
- 每新增 一个产品类型
- 就必须新增 一个对应的工厂
- 当产品数量很多时,工厂数量线性增多
- 而且只能描述单个产品的创建,无法表达“多个产品之间必须成套使用”的约束关系。
抽象工厂模式
改进:
引入「产品族(Product Family)」思想:
- 一个抽象工厂 不再只生产一个产品
- 而是 生产一整组相互关联的产品
- 每个具体工厂对应一个具体的产品族
工厂方法解决“造哪个”,
抽象工厂解决“成套怎么造”。
/**
* 日志 / 监控 / 埋点往往是相互关联、成套存在的
* 日志(log):记录运行信息
* 监控(error):上报异常
* 埋点(track):上报用户行为
*/
// 抽象工厂:定义一个“监控产品族”
interface MonitorFactory {
log(message: string): void;
track(event: string, params?: Record<string, any>): void;
error(error: Error): void;
}
// 具体工厂A:Sentry 产品族
class SentryFactory implements MonitorFactory {
log(message: string): void {
console.log(`[Log] ${message}`);
}
track(event: string, params?: Record<string, any>): void {
console.log(`[Track] ${event}`, params);
}
error(error: Error): void {
console.error(`[Error] ${error.message}`);
}
}
// 具体工厂B: Console 产品族
class ConsoleFactory implements MonitorFactory {
log(message: string) {
console.log('[Console log]', message);
}
track(event: string, params?: Record<string, any>) {
console.log('[Console track]', event, params);
}
error(error: Error) {
console.error('[Console error]', error);
}
}
//客户端只依赖抽象工厂 不知道也不关心具体实现是谁
function initMonitor(factory: MonitorFactory) {
factory.log('This is a log message');
factory.track('This is a track event');
factory.error(new Error('This is an error'));
}
//比如根据环境变量选择具体工厂
function createMonitorFactory(): MonitorFactory {
return process.env.NODE_ENV === 'production'
? new SentryFactory()
: new ConsoleFactory();
}