#typescript #inheritance
#typescript #наследование
Вопрос:
Пожалуйста, посмотрите на пример ниже.
class A {
public nodes: A;
constructor(data, instance: new (data) => any) {
this.nodes = data.children.map(child => new instance(child));
}
}
class B extends A {
public nodes: B;
constructor(data) {
super(data,B);
}
}
class C extends A {
public nodes: C;
constructor(data) {
super(data,C);
}
}
Я хочу создать узлы во всех классах, унаследованных от класса A, определив создание этих узлов в классе A. Теперь я сделал это, как написано выше. Я хочу знать, есть ли более приятный способ сделать это?
Комментарии:
1. Обеспечьте ввод для
data
instance
переменных и, и это было бы хорошо.2. @zerkms К сожалению, я не могу установить тип для
instance
, потому что это может быть любой унаследованный класс из класса A (то жеdata
самое, потому что это зависит от класса)3. Ваш код вообще работает? Потому что, просто прочитав его, я не уверен, что это так. Если вы вызываете
new B(data)
это, вызывается суперконструктор, и суперконструкторnew B
снова вызывается (какnew instance
). Который должен логически вызывать суперконструктор и переходить в бесконечную рекурсию. Или, может быть, просто выдать ошибку. Я полагаю, это зависит от того, какую формуdata
имеет. И я все еще не уверен, почему вы здесь не используете дженерики. Это не похоже на связь междуA
,B
, иC
. Вам может понадобиться композиция для решения вашей проблемы проектирования в первую очередь.4. @VLAZ относительно бесконечной рекурсии:
new instance
вызывается только для дочерних узлов (таким образом, он вызывается до тех пор, пока каждый последующий узел имеет дочерние узлы). о том, почему я не использую generic: запись мне нравитсяnew B <B> (data)
илиnew C<C>(data)
выглядит странно. Но в то же время я все равно хотел бы знать, какое из решений является лучшим…
Ответ №1:
О том, почему я не использую generic: запись like
new B<B>(data)
или newC<C>(data)
выглядит для меня странно.
Для этого вам нужно использовать дженерики, но B
и C
не обязательно должны быть универсальными. Они могут расширять определенные типы общей базы A
. Так что вы просто позвоните new B(data)
.
Мы можем получить некоторые из требуемых типов, просто посмотрев на использование.
this.nodes = data.children.map(child => new instance(child));
Эта строка здесь сообщает мне, что data
переменная, которая передается конструктору, имеет children
свойство. children
представляет собой массив , в котором каждый элемент может быть присвоен тому же типу , data
что и .
children
кажется, здесь требуется, и в этом случае терминальные узлы будут иметь {children: []}
. Это нормально, если это то, что вы хотите, но обычно это необязательно, так что конечные узлы вообще не имеют children
свойства.
type Recursive<OwnData> = OwnData amp; {
children?: Array<Recursive<OwnData>>;
}
Таким образом, мы получаем этот тип Recursive<OwnData>
, который является общим, который зависит от переменной OwnData
, где OwnData
представлены все свойства, отличные от children
.
Мы не знаем, что происходит внутри конструкторов отдельных потомков A
, и мы не знаем, какие уникальные переменные экземпляра они могут объявлять. Таким образом, для максимальной гибкости мы должны предположить, что свойства data
узла и свойства узла не всегда одинаковы. Это означает, что A
требуется два обобщения.
class A<DataProps, NodeType> {
public nodes: Array<NodeType>;
constructor(
data: Recursive<DataProps>,
instance: new (data: Recursive<DataProps>) => NodeType
) {
this.nodes = data.children ? data.children.map(child => new instance(child)) : [];
}
}
Теперь, когда мы создаем подкласс, мы расширяем определенную версию A<DataProps, NodeType>
. Мы можем передать сам подкласс в качестве типа для NodeType
.
type BData = {
b: number;
}
class B extends A<BData, B> {
public b: number;
constructor(data: Recursive<BData>) {
super(data, B);
this.b = data.b;
}
someFunc(): void {
console.log(this.b);
}
}
const bInst = new B({b: 1, children: [{b: 2}, {b: 3}]});
// each node of `bInst` is `B`
bInst.nodes.map(b => b.someFunc() );