#javascript #typescript #polymorphism
#javascript #typescript #полиморфизм
Вопрос:
Недавно я вернулся к JS / TS после того, как много кодировал в Rust, и это функция, по которой я действительно скучаю.
Итак, предположим, что у меня есть значение, которое может быть экземпляром Cat
или экземпляром Dog
, и в зависимости от того, из какого класса оно, я хочу выполнить другую логику. Поскольку Javascript не может этого сделать classname ==
(я думаю?), То, на что я обычно полагаюсь, также передает строку, указывающую класс следующим образом:
function groomAnimal(animal: Cat | Dog, animalType: 'cat' | 'dog') {
if (animalType === 'cat') {
(animal as Cat).trimNails();
} else {
(animal as Dog).trimFur();
}
}
Я ненавижу это, потому что это громоздко, и компилятор не может гарантировать, что вызывающий абонент правильно выполняет контракт. С другой стороны, в Rust это было бы довольно элегантно:
enum Animal {
Cat(CatData),
Dog(DogData),
}
fn groomAnimal(animal: Animal) {
match animal {
Cat(catData) => catData.trimNails(),
Dog(dogData) => dogData.trimFur(),
}
}
По сути, enum
сам по себе, который указывает «класс», также содержит данные «класса», и, таким образом, получение данных и проверка типа данных — это одна и та же операция
Есть ли в typescript какой-либо шаблон, который допускает сигнатуры полиморфных функций, гарантируя при компиляции, что код работает с правильными типами данных?
Ответ №1:
Сохранение отдельного значения, указывающего тип, очень подвержено ошибкам. Если Cat
и Dog
являются типами классов (а не только интерфейсами), вы можете использовать instanceof
для прямой проверки его типа:
class Cat {
trimNails() {}
}
class Dog {
trimFur() {}
}
function groomAnimal(animal: Cat | Dog) {
if (animal instanceof Cat) {
// animal must be Cat, so you can call trimNails()
animal.trimNails();
} else {
// animal must otherwise be a Dog, so you can call trimFur()
animal.trimFur();
}
}
Комментарии:
1. К сожалению, они являются интерфейсами. Они анализируются из JSON, загруженного с сервера. Я не думаю, что есть простой способ сделать их классами без каких-либо накладных расходов
2. Но на самом деле, это дает мне идею. Я мог бы просто обернуть интерфейсы в классы и использовать
instanceof
, что на самом деле очень похоже на решение rust. Я думаю, что это лучше, чем то, что я делал
Ответ №2:
Вы могли бы создать составную структуру с Cat/Dog
некоторым атрибутом идентификатора и некоторым атрибутом идентификатора, немного похожим на ваш подход, но по-другому.
Что-то вроде этого,
type Animal = Cat amp; { animalType: 'Cat' } | Dog amp; { animalType: 'Dog' }
Теперь ваш groomAnimal
метод будет принимать составленную структуру,
function groomAnimal(animal: Animal) {
if (animal.animalType === 'Cat') {
animal.trimNails();
} else {
animal.trimFur();
}
}
Вот один небольшой пример, в котором я создаю cat и передаю его groomAnimal
методу,
const tom = {
trimNails() {
console.log('Trimming nails')
}
}
groomAnimal({ ...tom, kind: 'Cat'})
В вашем случае вы сказали, что анализируете их из некоторых вызовов api. Вы уже могли подготовить свой Animal/Animals[]
объект при синтаксическом анализе и использовать его позже при подготовке.
И, конечно, компилятор может гарантировать, что вызывающий абонент действительно правильно выполняет контракт, как вы можете видеть из кода.
Комментарии:
1. Я знаю, что на данный момент это довольно старое, но, оглядываясь назад, это определенно правильное решение. Я не думаю, что оценил это в то время, но это очень чисто и просто