1

Please look at the example below.

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);
    }
}

I want to create nodes in all classes inherited from class A, defining the creation of these nodes in class A. Now I did this as written above. I want to know if there is a nicer way to do this?

4
  • Provide typing for data and instance variables and it would be good. Commented Jan 18, 2021 at 8:08
  • @zerkms Unfortunately, I cannot set the type for the instance, because it can be any inherited class from class A (same for data, cause it depends from the class) Commented Jan 18, 2021 at 8:20
  • 1
    Does your code even work? Because just by reading it, I am not sure it does. If you call new B(data) that calls the super constructor and the super constructor invokes new B again (as new instance). Which should logically call the super constructor and go into an infinite recursion. Or maybe just throw an error. I suppose it depends on what shape data has. And I'm still not sure why you're not using generics here. This doesn't feel like an is-a relationship between A, B, and C. You might need composition to solve your design problem first. Commented Jan 18, 2021 at 12:08
  • @VLAZ regarding infinite recursion: new instance is called only for child nodes (thus, it is called as long as each subsequent node has child nodes). about why I do not use generic: the entry like new B <B> (data) or new C<C>(data) looks strange to me. But at the same time I would still like to know which of the solutions is the best... Commented Jan 19, 2021 at 8:20

1 Answer 1

1

About why I do not use generic: the entry like new B<B>(data) or new C<C>(data) looks strange to me.

You need to use generics in order to do this, but B and C don't need to be generic. They can extend specific types of the generic base A. So you'll just call new B(data).

We can derive some of the required types just from looking at the usage.

this.nodes = data.children.map(child => new instance(child));

This line here tells me that the data variable which is passed to the constructor has a children property. children is an array where every element is assignable to the same type as data.

children seems to be required here, in which case terminal nodes would have {children: []}. That's fine if that's what you want, but typically it is optional such that terminal nodes do not have a children property at all.

type Recursive<OwnData> = OwnData & {
    children?: Array<Recursive<OwnData>>;
}

So we get this type Recursive<OwnData>, which is a generic that depends on a variable OwnData where OwnData represents all properties other than children.

We do not know what goes on inside the constructors of individual descendants of A and we do not know what unique instance variables they might declare. So for maximum flexibility, we should assume that the properties of data and the properties of a node aren't always the same. This means that A needs two generics.

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)) : [];
    }
}

Now when we create a subclass, we extend a specific version of A<DataProps, NodeType>. We can pass the subclass itself as the type for 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() );

Typescript Playground Link

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.