一、当JavaScript设计模式遇到TypeScript强类型
十年前我们用jQuery写业务逻辑时,模块化主要靠立即执行函数实现。而今大型前端项目中,TypeScript的类型系统和ES6+特性为我们打开新世界的大门。设计模式中的工厂模式不再需要复杂的new
操作符判断,单例模式可以直接用模块系统实现,观察者模式中的事件类型也能获得类型提示。
// 单例模式示例(TypeScript技术栈)
class AppConfig {
private static instance: AppConfig;
public readonly apiEndpoint: string;
private constructor() {
this.apiEndpoint = process.env.API_URL || "http://default.api";
}
static getInstance(): AppConfig {
if (!AppConfig.instance) {
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
}
const config = AppConfig.getInstance();
console.log(config.apiEndpoint); // 类型安全的访问
这个经过改造的单例模式,通过私有构造器和静态方法确保实例唯一性,readonly
修饰符防止配置被意外修改。相比传统JavaScript实现,TypeScript在编译阶段就能拦截大部分潜在错误。
二、泛型:给设计模式装上类型安全盔甲
2.1 容器类通用化改造
想象我们要实现一个能存储任意类型的数据容器,传统的JavaScript方案通常使用any
类型:
class AnyContainer {
constructor(private data: any) {}
getValue(): any {
return this.data;
}
}
const numContainer = new AnyContainer(42);
const strContainer = new AnyContainer("hello");
// 此处容易在运行时出错
const numResult: number = strContainer.getValue();
使用泛型重构后的版本:
class TypedContainer<T> {
constructor(private data: T) {}
getValue(): T {
return this.data;
}
}
const numberBox = new TypedContainer<number>(42);
const stringBox = new TypedContainer<string>("hello");
// 编译时报错:类型"string"的参数不能赋给类型"number"的参数
const result: number = stringBox.getValue();
泛型参数T
如同一个类型占位符,编译器能自动推断类型关系。在工厂模式中,我们可以创建类型安全的对象工厂:
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
constructor(public name: string) {}
makeSound() { console.log("Woof!"); }
}
class AnimalFactory {
static create<T extends Animal>(type: new (name: string) => T, name: string): T {
return new type(name);
}
}
const buddy = AnimalFactory.create(Dog, "Buddy");
buddy.makeSound(); // 输出"Woof!"且类型推断为Dog实例
2.2 泛型约束实战
在策略模式中,我们可以通过泛型约束确保策略的合规性:
interface SortingStrategy<T> {
sort(items: T[]): T[];
}
class NumberSorter implements SortingStrategy<number> {
sort(items: number[]): number[] {
return items.slice().sort((a, b) => a - b);
}
}
class StringSorter implements SortingStrategy<string> {
sort(items: string[]): string[] {
return items.slice().sort((a, b) => a.localeCompare(b));
}
}
function executeSort<T>(strategy: SortingStrategy<T>, data: T[]): T[] {
return strategy.sort(data);
}
const numbers = [3, 1, 4];
const sortedNumbers = executeSort(new NumberSorter(), numbers);
console.log(sortedNumbers); // [1, 3, 4] 且类型为number[]
三、装饰器:给传统模式注入超能力
3.1 方法装饰器实现日志追踪
假设我们需要给关键业务方法添加执行日志:
function logExecution(target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`调用方法 ${key},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`方法 ${key} 返回结果:`, result);
return result;
};
return descriptor;
}
class PaymentService {
@logExecution
processPayment(amount: number, currency: string): boolean {
// 模拟支付处理
return Math.random() > 0.5;
}
}
const payment = new PaymentService();
payment.processPayment(100, "USD");
// 控制台输出:
// 调用方法 processPayment,参数: [100, "USD"]
// 方法 processPayment 返回结果: true
3.2 属性装饰器实现响应式更新
实现一个简单的Vue式数据绑定:
const observersMap = new WeakMap<object, Function[]>();
function Observable(target: any, key: string) {
let value = target[key];
Object.defineProperty(target, key, {
get: () => value,
set: (newVal) => {
if (newVal !== value) {
value = newVal;
const observers = observersMap.get(target) || [];
observers.forEach(observer => observer());
}
}
});
}
class Store {
@Observable
counter = 0;
constructor() {
observersMap.set(this, []);
}
subscribe(callback: Function) {
const observers = observersMap.get(this)!;
observers.push(callback);
}
}
const store = new Store();
store.subscribe(() => console.log(`计数器更新: ${store.counter}`));
store.counter = 1; // 触发控制台输出
3.3 装饰器组合实现缓存策略
将多个装饰器组合使用:
function CacheResult(timeout: number) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
let lastResult: any;
let lastFetchTime = 0;
descriptor.value = function (...args: any[]) {
const now = Date.now();
if (now - lastFetchTime < timeout) {
console.log(`返回缓存结果:${lastResult}`);
return lastResult;
}
lastResult = originalMethod.apply(this, args);
lastFetchTime = Date.now();
return lastResult;
};
return descriptor;
}
}
class DataService {
@CacheResult(2000)
fetchData() {
console.log("实际请求数据...");
return Math.random();
}
}
const service = new DataService();
service.fetchData(); // 输出"实际请求数据..."
service.fetchData(); // 输出"返回缓存结果..."
setTimeout(() => service.fetchData(), 3000); // 再次请求新数据
四、手写极简依赖注入框架
4.1 核心容器实现
让我们用装饰器构建一个DI容器:
type Constructor<T> = new (...args: any[]) => T;
const serviceMap = new Map<symbol, Constructor<any>>();
const instanceMap = new Map<symbol, any>();
function Injectable(token: symbol) {
return function <T extends Constructor<any>>(target: T) {
serviceMap.set(token, target);
};
}
function Inject(token: symbol) {
return function (target: any, key: string) {
Object.defineProperty(target, key, {
get: () => {
if (!instanceMap.has(token)) {
const Constructor = serviceMap.get(token)!;
instanceMap.set(token, new Constructor());
}
return instanceMap.get(token);
},
enumerable: true,
configurable: true
});
};
}
// 使用示例
const LoggerToken = Symbol("ILogger");
const DatabaseToken = Symbol("IDatabase");
@Injectable(LoggerToken)
class LoggerService {
log(message: string) {
console.log(`[LOG] ${new Date().toISOString()} ${message}`);
}
}
@Injectable(DatabaseToken)
class DatabaseService {
query(sql: string) {
return `执行查询:${sql}`;
}
}
class App {
@Inject(LoggerToken)
logger!: LoggerService;
@Inject(DatabaseToken)
db!: DatabaseService;
run() {
this.logger.log("应用启动");
console.log(this.db.query("SELECT * FROM users"));
}
}
const app = new App();
app.run();
// 输出:
// [LOG] 2023-09-01T00:00:00.000Z 应用启动
// 执行查询:SELECT * FROM users
4.2 依赖注入的嵌套场景
处理多层依赖注入:
const ApiToken = Symbol("IApi");
@Injectable(ApiToken)
class ApiClient {
constructor(
@Inject(LoggerToken) private logger: LoggerService
) {}
fetchData() {
this.logger.log("发起API请求");
return { data: "API响应数据" };
}
}
class BusinessLogic {
@Inject(ApiToken)
api!: ApiClient;
@Inject(DatabaseToken)
db!: DatabaseService;
process() {
const data = this.api.fetchData();
this.db.query(`INSERT INTO logs VALUES ('${data.data}')`);
}
}
const logic = new BusinessLogic();
logic.process();
// 输出:
// [LOG] 发起API请求
// 执行查询:INSERT INTO logs VALUES ('API响应数据')
五、模式应用全景分析
5.1 典型应用场景
- 泛型:构建通用组件库、数据处理管道、类型安全的集合类
- 装饰器:AOP编程、功能增强(日志/缓存/权限)、元数据管理
- DI框架:大型应用架构、模块解耦、测试替身替换
5.2 技术优劣分析
优势:
- 泛型:提升代码复用率,编译时类型检查,智能提示
- 装饰器:非侵入式增强,声明式编程,关注点分离
- DI:降低耦合度,提高可测试性,依赖关系显式声明
注意事项:
- 装饰器仍是实验性特性,需配置
tsconfig.json
启用 - 过度使用装饰器会导致代码可读性下降
- DI框架实现应考虑循环依赖检测
- 泛型复杂度过高会增加理解成本
5.3 最佳实践总结
- 在DTO/API层优先使用泛型保持灵活性
- 装饰器适合处理横切关注点,但不建议修改方法核心逻辑
- 按模块划分DI容器,避免全局容器过度膨胀
- 结合
interface
和symbol
提升DI可维护性