Search code examples
typescriptgenericsfactory-pattern

Return type in a factory method


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

Playground

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.


Solution

  • 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!");
      }
    }