Given the following class hierarchy, if and how can I map concrete handler instance type to specific event class instance?
class Event {}
class EventA extends Event {
constructor(public readonly value: number) {
super();
}
}
class EventB extends Event {
constructor(public readonly message: string) {
super();
}
}
abstract class EventHandler<T> {
public abstract run(): T;
}
class EventAHandler extends EventHandler<number> {
constructor(private readonly event: EventA) {
super();
}
public run() {
return this.event.value + 1;
}
}
class EventBHandler extends EventHandler<string> {
constructor(private readonly event: EventB) {
super();
}
public run() {
return this.event.message + ' world!';
}
}
class EventHandlerFactory {
static createHandler<T extends Event>(event: T) {
if (event instanceof EventA) {
return new EventAHandler(event);
}
if (event instanceof EventB) {
return new EventBHandler(event);
}
throw new Error('Not implemented!');
}
}
const event = new EventA(1);
// T in createHandler is inferred as EventA
const handler = EventHandlerFactory.createHandler(event); // EventAHandler | EventBHandler
const result = handler.run(); // string | number -> should be number
What I would like, is to have handler
inferred (or to be) EventAHandler
instead of union type (without type assertion as
), so I could determine the type of a result of a specific handler.
You can use conditional types to make this work, though you will have to do a bit of casting:
type EventHandlerFromEvent<T extends Event> =
T extends EventA ? EventAHandler : EventBHandler;
class EventHandlerFactory {
static createHandler<T extends Event>(event: T): EventHandlerFromEvent<T> {
if (event instanceof EventA) {
return new EventAHandler(event) as EventHandlerFromEvent<T>;
}
if (event instanceof EventB) {
return new EventBHandler(event) as EventHandlerFromEvent<T>;
}
throw new Error("Not implemented!");
}
}