创建型(TS)

7 阅读4分钟

单例模式

保证一个类仅仅只有一个实例对象,并且提供一个对该实例对象的全局访问点。

如:浏览器中的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();
}