typescriptmultiple-inheritancemixins

Is there a wasy to emulate super with mixins in typescript?


I am using multiple inheritance via Mixins (using the alternative pattern). Is there a way to get something similar to 'super' with this pattern? Consider the example here

abstract class Activatable{
    private activated: boolean = false;
    constructor(){}
    public activate(): void {
        this.activated = true;
        console.log('Activatable activated')
  }
}

class SomethingElse{};

interface ThingParent extends Activatable{}

class ThingParent extends SomethingElse{
    public activate(): void {
        (this as Activatable).activate();
        let addSomeValueHere = true;
        console.log('Thing parent activated')
    }
}

applyMixins(ThingParent, [Activatable]);

class Thing extends ThingParent {
    constructor(){
        super()
    }
    public activate(): void {
        super.activate();
        
        console.log('Thing activated');
    }
}

let thing = new Thing();
thing.activate();



function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}

Thing extends ThingParent which extends via mixins a class called Activatable. When I call activate on Thing, I want to call activate on ThingParent and Activatable too, but it is only called on Activatable.

If I change the name of the function on ThingParent I can call that function directly, but ThingParent doesn't have access to Activatable via super (because it extends SomethingElse). I would then have to call both super.activate() and this.thingParentActivate() from thing.activate() so it's a pattern I would like to avoid if possible.

Is there an alternative?

(Link to code on typescript playground: https://www.typescriptlang.org/play?#code/IYIwzgLgTsDGEAJYBthjAggvAlgN2AlGQFMBvAWACgFaEAHKfQkhOCZiEgEwC4EQAe0GlgAOwQBeBADNgyMCQDc1OkkFjIUAK7xBUABQBKMgF9VdetpDIcsNrgJdj-PIJzcElGmroQAFjhgAHTsnDxSCNDayha+6poiJMHIggDmBgDk2BxOxKxhTjyZRhbmVOXUKGgYAMqCALYkAThiaQCiCuSmKlTUrVxQcrCsACqBbQAKwFAkYogkAB5cYtwYOZz5ZtRVqOgI461p07PzCEsrawj1TS1tnYrealY2dg65LC4Ibh5ecb4GFoYNBYRyEfJGUJg5xGXrxWikRDAbjcG4kABq8hiAAkSLNItFYj54rANGAkil0llDm0GDM5kjocVSsSEOVKlRqvsaWlzss5lceScGX9WaTNNE9IYTP81GBtPQ8cZ-uVntZbPZCp8jK53J4nvD5YqoFCPjC4fDZXRxeTSJSMpkee9wtwShbaOydlRgPR6MgAJ4AWRwi1aYAMQvp8wANAgANobPI2EgAXVhXuoiKiE150jEJAA7gcc8Zenc0qbwqWM30qDJtGJcBo2L6A8HQ5oDNw8fgeABhCD6fjif2xm2SwdQMDDsT+uNp0W0cc6KUhGT6dpwfwGAwgNAkAf6IxSAB8i7oAHkQAArEjwYJpZoXgtiSZQQTGiD+gBywCa4b3RRDxNRhBEHL9FUhdcoE3WBtwMMQ-xIY9JDPA01CvW9727GRWhIN8PzxL8DCtWhuyYPB+0nYJQPA-1FWjUiEEQppGNZDCbzvCAHyfF8CM-f0ABESDAWAmHoSdd33YCaPfOiGOYpDjwAH2UpjaEwrjgjEkhPjEbRkGQFl4nTVlTFMj1TNMIA)


Solution

  • As you know, JavaScript classes don't support multiple inheritance, so you need to do something to simulate it if you want to get that effect. Mixins are one way to do this, but they don't really support having conflicting method names. If you have conflicting method names then mixins will end up clobbering all but one of them, which is not what you want.

    If you want to call a particular "superclass"'s method on the "subclass"'s instance, you can do so directly with the Function.prototype.call() with the instance as the this arg. Instead of Activatable::activate(), you call Activatable.prototype.activate.call(this);.

    Let's try it out:

    interface ThingParent extends Activatable { }
    class ThingParent extends SomethingElse {
        public activate(): void {
            Activatable.prototype.activate.call(this);
            let addSomeValueHere = true;
            console.log('Thing parent activated')
        }
    }
    
    class Thing extends ThingParent {
        constructor() {
            super()
        }
        public activate(): void {
            ThingParent.prototype.activate.call(this);
            console.log('Thing activated');
        }
    }
    
    let thing = new Thing();
    thing.activate();
    // [LOG]: "Activatable activated" 
    // [LOG]: "Thing parent activated" 
    // [LOG]: "Thing activated" 
    

    That compiles okay and produces the output you expect.


    In more complex scenarios you may decide you need to do a combination of mixins and direct method calls. Or some other approach.

    Playground link to code