I have a base class implementing a bunch of methods, each returning a new reference to a copy of object to allow chaining.
class Base {
constructor(public name: string) {}
funcA(): Base { return new Base('FUNC_A') }
funcB(): Base { return new Base('FUNC_B') }
}
Additionally, I have a derived class, exposing only some of the methods of the base class, and also exposing its own methods. All of these methods should return a reference to the derived class object to allow for chaining.
class Derived extends Base {
constructor() { super('DERIVED') }
funcA(): Derived { return super.funcA() }
newFunc(): Derived { return new Derived() }
}
I run into a problem in this case. The object returned by the overridden method is still an instance of the base class, whether it's casted as the derived class or not, and none of the methods defined for the first time in the derived class are defined.
One workaround comes to my mind, which is not elegant. Instead of inheritance, I could use composition to contain an instance of base class object inside a derived class object. However, for this, I need an additional constructor to accept a base class object which should be accessible outside the class, and use it like funcA(): Derived { return new Derived(this.baseObject) }.
Is there a more elegant way of solving this problem?
I think this works in the way you intended:
Explanation:
Using
funcA(): thisinstead offuncA(): Basetells TypeScript, that the function returns an object of the same type as the class that it is called on and not alwaysBase. If called on an instance ofBaseit should return an instance ofBaseand if called onDerivedit should return an instance ofDerived.To correctly implement that, calling
new Base()would be wrong because it always returns an instance of base. To access the constructor of the actual instance, every javascript object has athis.constructorproperty (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor). So I callnew this.constructor()instead ofnew Base().this.constructorwill beBaseif called on an instance ofBaseandDerivedif called on an instance ofDerivedSadly typescript does not recoqnize
this.constructoras the correct type, which would be a constructor of typenew (...any[]) => this(See https://github.com/microsoft/TypeScript/issues/3841 ) so I just cast it toany. Maybe there is a better way to do that, that I don't know of.If you have to call the constructor more often, you can create a method for it, so you can write it easier:
Here is the TS Playground I used for testing: https://www.typescriptlang.org/play?#code/MYGwhgzhAEBCkFNoG8BQ1rAPYDsIBcAnAV2Hy0IAoAHYgIxAEthocwBbBALmgMMZwBzAJQoAvulYIA7gEk8+MDmAJKAOg1hCgiDyUBPANoBdYT3wALRjGTRCCfMUI4p06JUvW12BSTIVoSECcfWF1TW0IYQBuaAkMADNiZQBBSjNoTxs7BycXLLUcGXkCJRVKAHIAMQBVADkAYQB9FIrReOgk5Vh08yts+0dnTP7C4oUy1Wr65tg2uNQJVFBIGAARBH4ANwQAE2gEAA98BBxdmHgIJDQMHz5ScipRWwhiak3KtYBRACVZADUvmt5h0usA0hksigckMXK93oQ1GCIQsMEVpFVksBeiNrNDBnlcRAxnIJspVO1FqhlrgIFgQAg1CAsIJKOjoBttnt0kisRCSZjlOkYjS8PTGczWezLqoAERgWXCXmpHlgnrCEVAA