如何实现可通过类实例访问的静态单例成员?TypeScript事件优化
现有实现及问题
我在应用中定义了抽象类Event统一事件语义,通过继承实现具体事件:
abstract class Event { abstract type: EventType; } class UserBoughtProductEvent extends Event { type = new EventType('User Bought A Product'); }
但这种实现存在问题:每次实例化UserBoughtProductEvent都会新建EventType实例,导致不同事件实例的type属性不相等,无法正确用于事件处理器的映射判断。
临时方案的不足
目前的临时解决办法是把EventType实例定义为外部常量,再在事件类中引用:
const eventType = new EventType('User Bought A Product'); class UserBoughtProductEvent extends Event { type = eventType; }
这种方式让type成为类实例可访问的"静态"成员,但依赖开发者手动实现,容易出现重复创建EventType的失误,无法从代码层面强制保证单例。
核心需求
我需要构建EventType与EventHandler的映射关系,在以下场景中能正确判断事件类型是否存在对应处理器:
class Aggregate { private eventTypeToHandlerMap: Map<EventType, EventHandler>; constructor(map: Map<EventType, EventHandler>) { this.eventTypeToHandlerMap = map; } applyEvent(event: Event) { if (this.eventTypeToHandlerMap.has(event.type)) { // 执行对应处理器逻辑 } else { // 抛出错误或其他处理 } } }
补充:EventType的现有实现如下:
export class EventType { private readonly _value: string; constructor(input: string) { if(input.length === 0) { throw new Error('event type cannot be an empty string'); } else { this._value = input; } } get value() { return this._value; } }
可行解决方案
方案1:静态属性+实例getter
给每个事件子类定义静态的type属性,同时通过实例的getter返回该静态属性,确保所有实例共享同一个EventType实例:
abstract class Event { abstract get type(): EventType; } class UserBoughtProductEvent extends Event { // 静态属性存储唯一的EventType实例 public static readonly type = new EventType('User Bought A Product'); // 实例通过getter访问静态属性 get type(): EventType { return UserBoughtProductEvent.type; } }
这种方式既保证了type的单例性,又符合抽象类的约束,构建映射时可以直接用类的静态属性:
const handlerMap = new Map<EventType, EventHandler>([ [UserBoughtProductEvent.type, (event) => { // 处理用户购买产品事件的逻辑 }] ]);
方案2:装饰器自动注入
如果存在大量事件类,可以用装饰器自动生成静态type并绑定到实例,减少重复代码并避免手动失误:
// 定义装饰器,接收事件类型字符串 function EventTypeDecorator(typeValue: string) { return function<T extends {new(...args: any[]): Event}>(constructor: T) { // 给类添加静态type属性 constructor.type = new EventType(typeValue); // 返回继承原类的子类,实现实例的type getter return class extends constructor { get type() { return constructor.type; } }; }; } // 使用装饰器简化事件类定义 @EventTypeDecorator('User Bought A Product') class UserBoughtProductEvent extends Event {}
使用时同样可以通过UserBoughtProductEvent.type获取单例实例,实例的event.type也会指向同一个对象。
方案3:改造EventType为单例工厂
修改EventType类,通过静态工厂方法保证相同字符串对应同一个实例,从根源上避免重复创建:
export class EventType { // 缓存已创建的EventType实例 private static readonly instances = new Map<string, EventType>(); private readonly _value: string; // 私有构造函数,强制通过工厂方法创建实例 private constructor(input: string) { if(input.length === 0) { throw new Error('event type cannot be an empty string'); } this._value = input; } // 静态工厂方法,返回单例实例 public static getInstance(input: string): EventType { if(!this.instances.has(input)) { this.instances.set(input, new EventType(input)); } return this.instances.get(input)!; } get value() { return this._value; } }
事件类中直接使用工厂方法获取实例:
class UserBoughtProductEvent extends Event { type = EventType.getInstance('User Bought A Product'); }
这种方式即使开发者在多个地方调用getInstance传入相同字符串,也会拿到同一个实例,彻底解决相等性问题,同时不需要额外管理外部常量。
内容的提问来源于stack exchange,提问作者alaboudi

