У меня есть простой класс, реализующий шаблон посетителя:
abstract class MyNode {};
class MyNodeA extends MyNode {};
class MyNodeB extends MyNode {};
abstract class NodeVisitor {
abstract visitMyNodeA(node: MyNodeA): unknown;
abstract visitMyNodeB(node: MyNodeB): unknown;
public visit(node: MyNode) {
if (node instanceof MyNodeA) {
return this.visitMyNodeA(node);
} else if (node instanceof MyNodeB) {
return this.visitMyNodeB(node);
} else {
throw new Error('Unknown node type on visitor');
}
}
}
и позже я хочу иметь настраиваемые типы возврата для каждой функции посещения, когда я реализую NodeVisitor
class MyNodeVisitor extends NodeVisitor {
visitMyNodeA(node: MyNodeA): number {
return 1;
}
visitMyNodeB(node: MyNodeB): number {
return this.visit(new MyNodeA()) + 1;
}
}
но это генерирует ошибку, потому что компилятор TypeScript не понимает, что вызов visit
с параметром типа MyNodeA
перенаправляет на функцию visitMyNodeA
, которая теперь возвращает number
.
Как мне реализовать такое решение?
@jcalz Извините, вы правы. Пример, который у меня был раньше, не вызывал ошибки, поскольку я выполнял приведение типов к String. Обновлен пример, чтобы четко показать мою проблему
Да, компилятор сам не может понять это. Вы можете помочь ему сделать это за счет большей сложности (и меньшей безопасности типов внутри реализации visit()
). Я предлагаю дать visit()
подпись общий, тип возвращаемого значения которой — условный тип на основе полиморфный this
тип подклассов:
abstract class MyNode {myNode = "myNode"}
class MyNodeA extends MyNode {a = "a"}
class MyNodeB extends MyNode {b = "b"}
abstract class NodeVisitor {
abstract visitMyNodeA(node: MyNodeA): unknown;
abstract visitMyNodeB(node: MyNodeB): unknown;
// call signature
public visit<T extends MyNode>(
node: T
): T extends MyNodeA ? ReturnType<this["visitMyNodeA"]> :
T extends MyNodeB ? ReturnType<this["visitMyNodeB"]> :
never;
// implementation signature is wider
public visit(node: MyNode): unknown {
if (node instanceof MyNodeA) {
return this.visitMyNodeA(node);
} else if (node instanceof MyNodeB) {
return this.visitMyNodeB(node);
} else {
throw new Error("Unknown node type on visitor");
}
}
}
class MyNodeVisitor extends NodeVisitor {
visitMyNodeA(node: MyNodeA): number {
return 1;
}
visitMyNodeB(node: MyNodeB): number {
return this.visit(new MyNodeA()) + 1;
}
}
Это работает? Идея состоит в том, что вы проводите компилятор через анализ того, что если вы передадите MyNodeA
, то visit()
вернет результат this.visitMyNodeA(node)
, и то же самое для MyNodeB
.
Надеюсь, это поможет; удачи!
Могу я предложить public visit<T extends MyNode>(node: T): ReturnType<Extract<this[Exclude<keyof this, 'visit'>], (node: T) => any>>;
? ИТ-отдел должен извлечь тип возвращаемого значения на основе переданного параметра, если методы посещения* общедоступны. Может стать немного хрупким, если вы добавите дополнительные методы, соответствующие сигнатуре (node: MyNode) => any
, но это может быть вариантом :)
@jcalz Похоже, это решение работает, но только когда методы посещения * общедоступны, есть ли обходной путь, если это не так?
@TitianCernicova-Dragomir Мне это нравится; Я согласен с возможной хрупкостью, но, вероятно, есть обходные пути. @Komninos, если методы не являются общедоступными, вам будет очень трудно ссылаться на них программно (поскольку они больше не являются частью Pick<NodeVisitor, keyof NodeVisitor>
). Не уверен, есть ли у кого-то еще предложения по этому поводу, но обратите внимание, что это, похоже, отличается от заданного вопроса.
"это вызывает ошибку"... какая конкретно ошибка? Не могли бы вы превратить это в минимальный воспроизводимый пример и, возможно, даже дать ссылку на онлайн-среду разработки, такую как Playground, которая демонстрирует проблему?