在 TypeScript 开发中,设计模式的合理应用能够显著提升代码的可维护性与扩展性。本章聚焦实用设计模式,通过“场景-实现-优势”三步分析法,结合 TypeScript 类型系统特性,详解单例模式与工厂模式的实战应用,并通过案例与练习深化理解。
应用场景
当需要确保某个类在应用生命周期内仅有一个实例(如全局状态管理、配置管理器、数据库连接池)时,单例模式是最优解。在前端开发中,Vuex/Pinia 的 Store 实例、全局事件总线等核心模块常采用此模式,避免多实例导致的状态不一致问题。
实现方案
单例模式的核心在于通过私有构造函数阻止外部实例化,并通过静态方法控制唯一实例的创建与访问。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 中可通过接口与联合类型约束输入输出,确保类型安全:
// 产品接口:定义统一行为
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 中的实现,核心优势在于类型系统对模式约束的强化。以工厂模式为例:
'A' | 'B' 确保仅允许预定义类型作为参数,避免 JavaScript 中因传入错误字符串(如 'a' 小写)导致的运行时错误。 Product 强制所有产品实现 operation() 方法,确保调用方无需类型断言即可安全使用。实战案例:环境感知的日志工厂
根据开发/生产环境创建不同日志实例(控制台日志/文件日志),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 方法存在
需求:实现一个“缓存管理器”,通过装饰器自动缓存类方法的返回值,且确保缓存管理器为单例实例(避免重复缓存)。
实现思路:
get/set 方法。 参考实现:
// 单例缓存管理器
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 透明拦截方法调用,无需侵入业务逻辑即可实现缓存。 CacheManager 添加泛型支持(如 get<T>(key: string): T | undefined),确保缓存值类型安全。