Как динамически создавать экземпляры разных классов, с доступом к их типам?

Например, есть два класса
class Animal {
  async getEntity() {
    return await Promise.resolve('There is a string')
  }
  async specificAnimalMethod() {
    return await Promise.resolve('Animal Promise resolved')
  }
}

class Human {
  async getEntity() {
    return await Promise.resolve(777)
  }
  async specificHumanMethod() {
    return await Promise.resolve('Human Promise resolved')
  }
}


Иногда нужно создать экземпляр одного класса, иногда - другого. И дальше работать с методами этого экземпляра. Эти методы иногда пересекаются, иногда уникальны для каждого класса. Те, что пересекаются, могут возвращать одинаковый тип данных, а могут - разные типы. Заведомо я не знаю, экземпляр какого класса нужно создать. И могу ориентироваться только на строку в пропсах, которые прилетают с бэка:

{ instance: 'animal' }
// либо
{ instance: 'human' }


Если прилетел animal - то нужно инстанцироваться от Animal, если human - то от Human.
Самое очевидное, что приходит в голову - это написать словарь классов и по ключу выдергивать из него нужный:

const dict = {
  animal: Animal,
  human: Human
};

const get = (key: keyof typeof dict) => {
  return dict[key];
};

const TargetClass = get(props.instance); // Допустим props.instance === 'animal'
const instance = new TargetClass();


И в обычном JS такая история бы прокатила, но в случае с TS - у меня как бы нет доступа к непересекающимся методам. Например, инстанцировавшись от Animal, я не могу вызвать его метод specificAnimalMethod, потому что

Property 'specificAnimalMethod' does not exist on type 'Animal | Human'. Property 'specificAnimalMethod' does not exist on type 'Human'


Что вполне ожидаемо, т.к. возвращаемый тип этого геттера: typeof Animal | typeof Human
А также я не знаю точный тип, который вернет метод getEntity, который есть у обоих классов, но возвращающий разные типы. И по факту вообще в моем случае возвращаемым типом окажется any.

instance.getEntity().then((res) => console.log(res)) // (parameter) res: any


Как можно решить такую проблему? Полагаю, что можно было бы чего-нибудь наколдовать с использованием InstanceType, но не могу сообразить, как.
  • Вопрос задан
  • 138 просмотров
Решения вопроса 1
Aetae
@Aetae Куратор тега TypeScript
Тлен
Т.е. по сути у тебя (определяемый в рантайме) инстанс неизвестного класса, абсолютно рандомного, раз даже сигнатуры схожих методов не совпадают.

Ну тогда какбэ остаётся только проверять руками, что за инстанс имеем перед использованием. Типа if (instance instanceof Animal) { ... }, как иначе то? Как-то ты же сейчас проверяешь какой метод вызвать specificAnimalMethod или specificHumanMethod?
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Alexandroppolus
@Alexandroppolus
кодир
Какова судьба экземпляров? Они на одно действие, или остаются жить-поживать?

Если на одно действие, то можно замутить словарь функций, в зависимости от строки брать нужную функцию и выполнять. Внутри выбранной функции ты можешь произвольно создавать те или иные экземпляры и с ними работать.

Иначе, классы должны реализовывать некий общий интерфейс (либо, как вариант, наследоваться от общего базового класса, тут не суть важно). То есть выбор конкретного класса будет только один раз, при создании экземпляра. Далее ты забываешь, какой там класс, и работаешь в рамках интерфейса (с одинаковыми методами и свойствами). Методы вроде getEntity из твоего примера, опять же, возвращают экземпляр какого то другого общего для всех энтитей интерфейса, реализуя паттерн "фабричный метод". Вот примерно так.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы