设计模式应用

在 TypeScript 开发中,设计模式的合理应用能够显著提升代码的可维护性与扩展性。本章聚焦实用设计模式,通过“场景-实现-优势”三步分析法,结合 TypeScript 类型系统特性,详解单例模式与工厂模式的实战应用,并通过案例与练习深化理解。

单例模式:全局唯一实例的类型化实现

应用场景
当需要确保某个类在应用生命周期内仅有一个实例(如全局状态管理、配置管理器、数据库连接池)时,单例模式是最优解。在前端开发中,Vuex/Pinia 的 Store 实例、全局事件总线等核心模块常采用此模式,避免多实例导致的状态不一致问题。

实现方案
单例模式的核心在于通过私有构造函数阻止外部实例化,并通过静态方法控制唯一实例的创建与访问。TypeScript 代码实现如下:

typescript
复制代码
class Singleton {
  // 静态属性存储唯一实例
  private static instance: Singleton;

  // 私有构造函数:禁止外部通过 new 创建实例
  private constructor() {}

  // 静态方法:提供全局访问点,确保实例唯一
  static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  // 示例业务方法
  public someMethod(): void {
    console.log("单例实例的业务逻辑");
  }
}

核心优势

  • 实例唯一性:通过 private constructor() 阻止外部直接实例化(如 new Singleton() 会触发编译错误),确保仅通过 getInstance() 获取实例。
  • 延迟初始化:实例在首次调用 getInstance() 时创建,避免启动时资源浪费。
  • 全局访问点:静态方法提供统一入口,便于跨模块共享状态或资源。

TypeScript 类型强化:通过 private static instance: Singleton 的类型注解,在编译阶段即确保实例类型正确,避免 JavaScript 中因类型模糊导致的“伪单例”问题(如意外创建多个实例)。

工厂模式:对象创建的类型化解耦

应用场景
当对象创建逻辑复杂(如依赖环境配置、需根据参数动态选择类型),或需隔离对象创建与使用逻辑时(如后端服务注册、插件系统),工厂模式可有效降低代码耦合度。典型案例包括根据用户角色创建不同权限实例、根据环境变量选择日志输出方式等。

实现方案
工厂模式通过抽象对象创建过程,将“创建逻辑”与“使用逻辑”分离。TypeScript 中可通过接口与联合类型约束输入输出,确保类型安全:

typescript
复制代码
// 产品接口:定义统一行为
interface Product {
  operation(): string;
}

// 具体产品实现
class ProductA implements Product {
  operation(): string {
    return "ProductA 操作结果";
  }
}

class ProductB implements Product {
  operation(): string {
    return "ProductB 操作结果";
  }
}

// 工厂类:根据类型参数创建对应产品
class ProductFactory {
  // 类型约束:仅允许 'A' 或 'B' 作为输入
  createProduct(type: 'A' | 'B'): Product {
    switch (type) {
      case 'A':
        return new ProductA();
      case 'B':
        return new ProductB();
      default:
        // TypeScript 类型检查确保此处不会进入默认分支
        throw new Error(`不支持的产品类型: ${type}`);
    }
  }
}

核心优势

  • 解耦创建与使用:调用方无需关注 ProductA/ProductB 的具体实现,只需通过工厂获取实例,降低代码依赖。
  • 扩展性提升:新增产品类型时,仅需扩展工厂类(如添加 ProductC 及对应 case 'C'),无需修改调用方代码。
  • 类型安全保障:通过 type: 'A' | 'B' 约束输入,避免传入无效类型(如 createProduct('C') 会触发编译错误);返回值类型 Product 确保调用方可安全调用 operation() 方法。

TypeScript 类型优势:从“运行时错误”到“编译时拦截”

设计模式在 TypeScript 中的实现,核心优势在于类型系统对模式约束的强化。以工厂模式为例:

  • 输入验证:联合类型 'A' | 'B' 确保仅允许预定义类型作为参数,避免 JavaScript 中因传入错误字符串(如 'a' 小写)导致的运行时错误。
  • 输出一致性:返回值类型 Product 强制所有产品实现 operation() 方法,确保调用方无需类型断言即可安全使用。

实战案例:环境感知的日志工厂
根据开发/生产环境创建不同日志实例(控制台日志/文件日志),TypeScript 类型约束确保日志行为一致:

typescript
复制代码
// 日志接口:定义统一日志行为
interface Logger {
  log(message: string): void;
}

// 开发环境日志:输出到控制台
class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[DEV] ${message}`);
  }
}

// 生产环境日志:模拟写入文件(实际项目可集成 fs 模块)
class FileLogger implements Logger {
  log(message: string): void {
    // 生产环境逻辑:写入日志文件
    console.log(`[PROD] 写入文件: ${message}`);
  }
}

// 日志工厂:根据环境创建日志实例
class LogFactory {
  // 环境类型约束:仅允许 'development' 或 'production'
  static createLogger(env: 'development' | 'production'): Logger {
    return env === 'development' ? new ConsoleLogger() : new FileLogger();
  }
}

// 使用示例
const logger = LogFactory.createLogger(process.env.NODE_ENV as 'development' | 'production');
logger.log("应用启动成功"); // 类型安全:确保 log 方法存在

实践练习:装饰器 + 单例模式实现缓存管理器

需求:实现一个“缓存管理器”,通过装饰器自动缓存类方法的返回值,且确保缓存管理器为单例实例(避免重复缓存)。

实现思路

  1. 单例缓存管理器:存储缓存数据,提供 get/set 方法。
  2. 方法装饰器:拦截目标方法调用,优先从缓存获取结果,未命中时执行方法并缓存返回值。

参考实现

typescript
复制代码
// 单例缓存管理器
class CacheManager {
  private static instance: CacheManager;
  private cache: Map<string, any>;

  private constructor() {
    this.cache = new Map();
  }

  static getInstance(): CacheManager {
    if (!CacheManager.instance) {
      CacheManager.instance = new CacheManager();
    }
    return CacheManager.instance;
  }

  get(key: string): any | undefined {
    return this.cache.get(key);
  }

  set(key: string, value: any): void {
    this.cache.set(key, value);
  }
}

// 缓存装饰器:自动缓存方法返回值(key 为方法名+参数序列化)
function cacheable(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const cacheManager = CacheManager.getInstance();

  descriptor.value = async function (...args: any[]) {
    // 生成缓存 key:方法名 + 参数序列化
    const key = `${methodName}-${JSON.stringify(args)}`;
    const cachedValue = cacheManager.get(key);

    if (cachedValue) {
      console.log(`[缓存命中] ${key}`);
      return cachedValue;
    }

    const result = await originalMethod.apply(this, args);
    cacheManager.set(key, result);
    console.log(`[缓存设置] ${key}`);
    return result;
  };

  return descriptor;
}

// 使用示例:带缓存的用户服务
class UserService {
  @cacheable
  async getUserById(id: number): Promise<{ id: number; name: string }> {
    console.log(`[API] 获取用户 ${id} 数据`);
    // 模拟 API 请求延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    return { id, name: `用户${id}` };
  }
}

// 测试:两次调用同一方法,第二次命中缓存
const userService = new UserService();
userService.getUserById(1); // [API] 获取用户 1 数据 → [缓存设置] getUserById-[1]
userService.getUserById(1); // [缓存命中] getUserById-[1] → 直接返回缓存结果

关键点

  • 单例 CacheManager 确保全应用共享同一缓存池,避免重复缓存。
  • 装饰器 @cacheable 透明拦截方法调用,无需侵入业务逻辑即可实现缓存。
  • TypeScript 类型系统可进一步强化:为 CacheManager 添加泛型支持(如 get<T>(key: string): T | undefined),确保缓存值类型安全。