Search code examples
typescriptclassinheritancemixins

How to inherit from different class based on Generic?


I've been hitting my head against this problem for some days now and seem to have reached my TS capabilities and would be very grateful for some assistance.

I have some fairly convoluted types but I will try to simplify it for this purpose.

Playground is here

Say I have two classes Score and Metric that each have two derived classes that should implement other methods and have slightly different properties, so I want to make them generic. The scores can all derive from the same base class but my Metrics need to derive from different classes that I don't own.

I thought I could achieve this with Mixins, i.e. define the common methods in a class factory and mix in the constructor for either of the implementations but I fail at that condition

Throughout my project I have been using interfaces A and B to differentiate between the two different functionalities or keep them as generic Kind which extends A or B where the share functionality resides.

However, now I face the issue that I need to use a different constructor depending on the implementation rather than just a type. For my purposes I don't actually need access to the additional methods of the derived classes but I need to create objects at runtime that inherit the correct method from either A or B I suspect that this might not actually have a solution and I would need to move away from my A, B conditioning pattern but to be honest I don't know how else to handle my inheritances...

If anybody could confirm my suspicions whether there is a solution for this or whether I've hit a roadblock here and if so could recommend a better pattern for my abstractions, I would be immensely grateful


Solution

  • I ended up using this code. Kellys gave me the right hint, but for me, having multiple of these patterns with the main differentiation being the Kind, this works a little better. I pass an object of type Kind into the constructor (which is ok for me since I can do it in the constructor of the susclasses)

    interface A { isA:true}
    interface B { isB:true}
    
    class BaseA {
        update(){/**do something */}
    }
    
    class BaseB {
        update(){/**do something else */}
    }
    
    interface Base{
        update:()=>void;//the methods here should be available when referencing a generic metric
    }
    
    type BaseConstructor = new (...args:any[])=>Base
    
    function MetricFactory<MetricType extends BaseConstructor>(BaseX:MetricType){
        return class extends BaseX{
            commonMethod(){}
        }
    }
    
    class MetricB extends MetricFactory(BaseB){}
    class MetricA extends MetricFactory(BaseA){
        someAdditionalMethod(){return this.commonMethod()}
    }
    function Metric<Kind extends A|B>(kind:Kind) {
        return (kind as A).isA !== undefined ? new MetricA(): new MetricB() 
    }
    
    class Score<Kind extends A|B>{
        constructor(kind: Kind) {
            this.genericMetric = Metric(kind) as Kind extends A ? MetricA: MetricB
        }
        genericMetric: Kind extends A ? MetricA: MetricB
    }
    
    class ScoreA extends Score<A>{
        constructor(){super({isA:true})}
    }
    
    let myScore = new ScoreA()
    
    myScore.genericMetric.update() //does something
    myScore.genericMetric.commonMethod() //does common thing
    myScore.genericMetric.someAdditionalMethod() //does additional thing