Недавно я вернулся к 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
, который указывает «класс», также содержит данные «класса», и, таким образом, получение данных и проверка типа данных являются одной и той же операцией.
Есть ли какой-либо шаблон в машинописном тексте, который позволяет использовать полиморфные сигнатуры функций, обеспечивая при этом во время компиляции, что код работает с правильными типами данных?
Сохранение отдельного значения, указывающего тип, очень подвержено ошибкам. Если 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();
}
}
Но на самом деле, это дает мне идею. Я мог бы просто обернуть интерфейсы в классы и использовать instanceof
, что на самом деле очень похоже на решение для ржавчины. Я думаю, это лучше, чем то, что я делал
Вы можете создать компонуемую структуру с Cat/Dog
и некоторым атрибутом идентификатора, немного похожим на ваш подход, но по-другому.
Что-то вроде этого,
type Animal = Cat & { animalType: 'Cat' } | Dog & { animalType: 'Dog' }
Ваш groomAnimal
метод теперь будет принимать составную структуру,
function groomAnimal(animal: Animal) {
if (animal.animalType === 'Cat') {
animal.trimNails();
} else {
animal.trimFur();
}
}
Вот один небольшой пример, где я создаю кота и передаю его методу groomAnimal
,
const tom = {
trimNails() {
console.info('Trimming nails')
}
}
groomAnimal({ ...tom, kind: 'Cat'})
В вашем случае вы сказали, что анализируете их из некоторых вызовов API. Вы уже можете подготовить свой объект Animal/Animals[]
при синтаксическом анализе и использовать его позже при уходе.
И, конечно же, компилятор может гарантировать, что вызывающая сторона действительно правильно выполняет контракт, как вы можете видеть из кода.
Я знаю, что на данный момент это довольно старо, но, оглядываясь назад, это определенно правильное решение. Я не думаю, что оценил это в то время, но это очень чисто и прямолинейно.
К сожалению, это интерфейсы. Они анализируются из JSON, загруженного с сервера. Я не думаю, что есть простой способ сделать их классами без каких-либо накладных расходов.