153

I want to save my time and reuse common code across classes that extend PIXI classes (a 2d webGl renderer library).

Object Interfaces:

module Game.Core {
    export interface IObject {}

    export interface IManagedObject extends IObject{
        getKeyInManager(key: string): string;
        setKeyInManager(key: string): IObject;
    }
}

My issue is that the code inside getKeyInManager and setKeyInManager will not change and I want to reuse it, not to duplicate it, here is the implementation:

export class ObjectThatShouldAlsoBeExtended{
    private _keyInManager: string;

    public getKeyInManager(key: string): string{
        return this._keyInManager;
    }

    public setKeyInManager(key: string): DisplayObject{
        this._keyInManager = key;
        return this;
    }
}

What I want to do is to automatically add, through a Manager.add(), the key used in the manager to reference the object inside the object itself in its property _keyInManager.

So, let's take an example with a Texture. Here goes the TextureManager

module Game.Managers {
    export class TextureManager extends Game.Managers.Manager {

        public createFromLocalImage(name: string, relativePath: string): Game.Core.Texture{
            return this.add(name, Game.Core.Texture.fromImage("/" + relativePath)).get(name);
        }
    }
}

When I do this.add(), I want the Game.Managers.Manager add() method to call a method which would exist on the object returned by Game.Core.Texture.fromImage("/" + relativePath). This object, in this case would be a Texture:

module Game.Core {
    // I must extend PIXI.Texture, but I need to inject the methods in IManagedObject.
    export class Texture extends PIXI.Texture {

    }
}

I know that IManagedObject is an interface and cannot contain implementation, but I don't know what to write to inject the class ObjectThatShouldAlsoBeExtended inside my Texture class. Knowing that the same process would be required for Sprite, TilingSprite, Layer and more.

I need experienced TypeScript feedback/advice here, it must be possible to do it, but not by multiple extends since only one is possible at the time, I didn't find any other solution.

3
  • 19
    Just a tip, whenever I come across a multiple inheritance problem, I try to remind myself to think "favor composition over inheritance" to see if that'll do the job.
    – bubbleking
    Commented Mar 15, 2017 at 22:38
  • 3
    Agreed. Wasn't thinking that way 2y ago ;) Commented Mar 16, 2017 at 9:04
  • 11
    @bubbleking how would favouring composition over inheritance apply here?
    – Seanny123
    Commented Jul 31, 2017 at 23:53

12 Answers 12

133

There is a little known feature in TypeScript that allows you to use Mixins to create re-usable small objects. You can compose these into larger objects using multiple inheritance (multiple inheritance is not allowed for classes, but it is allowed for mixins - which are like interfaces with an associated implenentation).

More information on TypeScript Mixins

I think you could use this technique to share common components between many classes in your game and to re-use many of these components from a single class in your game:

Here is a quick Mixins demo... first, the flavours that you want to mix:

class CanEat {
    public eat() {
        alert('Munch Munch.');
    }
}

class CanSleep {
    sleep() {
        alert('Zzzzzzz.');
    }
}

Then the magic method for Mixin creation (you only need this once somewhere in your program...)

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

And then you can create classes with multiple inheritance from mixin flavours:

class Being implements CanEat, CanSleep {
        eat: () => void;
        sleep: () => void;
}
applyMixins (Being, [CanEat, CanSleep]);

Note that there is no actual implementation in this class - just enough to make it pass the requirements of the "interfaces". But when we use this class - it all works.

var being = new Being();

// Zzzzzzz.
being.sleep();
5
  • 5
    Typescript 2.2 now supports Mixins Commented Apr 27, 2017 at 11:52
  • 1
    @FlavienVolken Do you know why Microsoft kept the old mixins section in their handbook documentation? By the way, the releases notes are realy difficult to understand for a beginner in TS like me. Any link for a tutorial with TS 2.2+ mixins? Thanks. Commented Jul 3, 2017 at 12:34
  • No idea, and honestly I would strongly recommend using a class cluster, a wrapper or facade pattern instead of mixins whenever you can, doing runtime mixins are not are not my cup of tea. It's not because we can that we should. Commented Jul 3, 2017 at 15:41
  • 4
    The "old way" to do mixins shown in this example is simpler than the "new way" (Typescript 2.2+). I don't know why they made it so difficult.
    – Franc
    Commented Jul 14, 2017 at 15:23
  • 2
    The reason is the "old way" can't get the type correctly.
    – unional
    Commented Aug 4, 2017 at 7:09
38
+50

I would suggest using the new mixins approach described there: https://blogs.msdn.microsoft.com/typescript/2017/02/22/announcing-typescript-2-2/

This approach is better, than the "applyMixins" approach described by Fenton, because the autocompiler would help you and show all the methods / properties from the both base and 2nd inheritance classes.

This approach might be checked on the TS Playground site.

Here is the implementation:

class MainClass {
    testMainClass() {
        alert("testMainClass");
    }
}

const addSecondInheritance = (BaseClass: { new(...args) }) => {
    return class extends BaseClass {
        testSecondInheritance() {
            alert("testSecondInheritance");
        }
    }
}

// Prepare the new class, which "inherits" 2 classes (MainClass and the cass declared in the addSecondInheritance method)
const SecondInheritanceClass = addSecondInheritance(MainClass);
// Create object from the new prepared class
const secondInheritanceObj = new SecondInheritanceClass();
secondInheritanceObj.testMainClass();
secondInheritanceObj.testSecondInheritance();
12
  • 1
    Is SecondInheritanceClass not defined on purpose or am I missing something? When loading this code in to the TS playground, it says expecting =>. Finally, could you break down exactly what is happening in the addSecondInheritance function, such as what's the purpose of new (...args)?
    – Seanny123
    Commented Aug 4, 2017 at 3:59
  • The main point of such mixins implementation that ALL the methods and properties of the both classes will be shown in the autocomplete IDE help. If you want, you can define the 2nd class and use the approach suggested by Fenton, but in this case IDE autocomplete won't work. {new (...args)} - this code describes an object which should be a class (you could read about TS interfaces more in the handbook: typescriptlang.org/docs/handbook/interfaces.html Commented Aug 4, 2017 at 19:01
  • 2
    the problem here is that TS still don't have a clue about modified class. I can type secondInheritanceObj.some() and don't get a warning message.
    – s-f
    Commented Apr 11, 2018 at 8:55
  • How to check if the Mixed class obey interfaces?
    – luthfianto
    Commented Aug 20, 2018 at 9:27
  • @Mark Dolbyrev > how about a second class with attribute and methode that use this attribute?
    – Mahefa
    Commented Mar 15, 2021 at 17:52
23

TypeScript supports decorators, and using that feature plus a little library called typescript-mix you can use mixins to have multiple inheritance with just a couple of lines

// The following line is only for intellisense to work
interface Shopperholic extends Buyer, Transportable {}

class Shopperholic {
  // The following line is where we "extend" from other 2 classes
  @use( Buyer, Transportable ) this 
  price = 2000;
}
1
  • 1
    In my case, I can't use interface and class with the same name at the same time, typescript gives an error. Also it says this implicitly has any type
    – Normal
    Commented Feb 7 at 16:35
15

Unfortunately typescript does not support multiple inheritance. Therefore there is no completely trivial answer, you will probably have to restructure your program

Here are a few suggestions:

  • If this additional class contains behaviour that many of your subclasses share, it makes sense to insert it into the class hierarchy, somewhere at the top. Maybe you could derive the common superclass of Sprite, Texture, Layer, ... from this class ? This would be a good choice, if you can find a good spot in the type hirarchy. But I would not recommend to just insert this class at a random point. Inheritance expresses an "Is a - relationship" e.g. a dog is an animal, a texture is an instance of this class. You would have to ask yourself, if this really models the relationship between the objects in your code. A logical inheritance tree is very valuable

  • If the additional class does not fit logically into the type hierarchy, you could use aggregation. That means that you add an instance variable of the type of this class to a common superclass of Sprite, Texture, Layer, ... Then you can access the variable with its getter/setter in all subclasses. This models a "Has a - relationship".

  • You could also convert your class into an interface. Then you could extend the interface with all your classes but would have to implement the methods correctly in each class. This means some code redundancy but in this case not much.

You have to decide for yourself which approach you like best. Personally I would recommend to convert the class to an interface.

One tip: Typescript offers properties, which are syntactic sugar for getters and setters. You might want to take a look at this: http://blogs.microsoft.co.il/gilf/2013/01/22/creating-properties-in-typescript/

5
  • 3
    Interesting. 1) I cannot do that, simply because I extends PIXI and I cannot change the library to add another class on top of it. 2) It is one of the possible solution I could use, but I would have prefered to avoid it if possible. 3) I definitively don't want to duplicate that code, it may be simple now, but what's gonna happen next? I've worked on this program only a day and I'll add a lot more later, not a good solution for me. I'll take a look at the tip, thanks for the detailled answer. Commented Nov 16, 2014 at 1:36
  • Interesting link indeed, but I don't see any use case to solve the problem here. Commented Nov 16, 2014 at 1:41
  • If all of these classes extend PIXI, then just make the ObjectThatShouldAlsoBeExtended class extend PIXI and derive the Texture, Sprite, ... Classes from that. That's what I meant with inserting the class in the type hirarchy
    – lhk
    Commented Nov 16, 2014 at 8:57
  • PIXI itself isn't a class, it's a module, it cannot be extended, but you're right, that would have been a viable option if it was! Commented Nov 16, 2014 at 12:06
  • 1
    So each of the classes extends another class from the PIXI module ? Then you're right, you can't insert the ObjectThatShouldAlso class into the type hirarchy. That's not possible. You could still choose the has-a relationship or the interface. Since you want to describe a common behaviour that all of your classes share, I would recommend using an interface. That's the cleanest design, even if code is duplicated.
    – lhk
    Commented Nov 16, 2014 at 13:39
15

I think there is a much better approach, that allows for solid type-safety and scalability.

First declare interfaces that you want to implement on your target class:

interface IBar {
  doBarThings(): void;
}

interface IBazz {
  doBazzThings(): void;
}

class Foo implements IBar, IBazz {}

Now we have to add the implementation to the Foo class. We can use class mixins that also implements these interfaces:

class Base {}

type Constructor<I = Base> = new (...args: any[]) => I;

function Bar<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBar {
    public doBarThings() {
      console.log("Do bar!");
    }
  };
}

function Bazz<T extends Constructor>(constructor: T = Base as any) {
  return class extends constructor implements IBazz {
    public doBazzThings() {
      console.log("Do bazz!");
    }
  };
}

Extend the Foo class with the class mixins:

class Foo extends Bar(Bazz()) implements IBar, IBazz {
  public doBarThings() {
    super.doBarThings();
    console.log("Override mixin");
  }
}

const foo = new Foo();
foo.doBazzThings(); // Do bazz!
foo.doBarThings(); // Do bar! // Override mixin
2
  • 1
    What are the returned type of the functions Bar and Bazz ? Commented Jul 18, 2020 at 6:39
  • 1
    Best one for me, however, I don't know why you need the implements part since it make you redefine all interfaces. Here is a simpler example without implements that works perfectly: codepen.io/seb-rom-o/pen/vYPEzGW?editors=0012
    – TOPKAT
    Commented Dec 26, 2023 at 17:40
3

A very hacky solution would be to loop through the class you want to inherit from adding the functions one by one to the new parent class

class ChildA {
    public static x = 5
}

class ChildB {
    public static y = 6
}

class Parent {}

for (const property in ChildA) {
    Parent[property] = ChildA[property]
}
for (const property in ChildB) {
    Parent[property] = ChildB[property]
}


Parent.x
// 5
Parent.y
// 6

All properties of ChildA and ChildB can now be accessed from the Parent class, however they will not be recognised meaning that you will receive warnings such as Property 'x' does not exist on 'typeof Parent'

1

In design patterns there is a principle called "favouring composition over inheritance". It says instead of inheriting Class B from Class A ,put an instance of class A inside class B as a property and then you can use functionalities of class A inside class B. You can see some examples of that here and here.

1

In my case I used concatenative inheritance. Maybe for someone this way will be helpful:

class Sprite {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

class Plane extends Sprite {
  fly(): string {
    return 'I can Fly!'
  }
}

class Enemy {
  isEnemy = true;
}

class Player {
  isPlayer = true;
}

// You can create factory functions to create new instances
const enemyPlane = Object.assign(new Plane(1, 1), new Enemy());
const playerPlane = Object.assign(new Plane(2, 2), new Player());

Also I recommend reading Eric Elliott's articles about js inheritance:

  1. The Heart & Soul of Prototypal OO: Concatenative Inheritance
  2. 3 Different Kinds of Prototypal Inheritance
1
  • That's one way to go, never thought of that. Kind of a hack, but not so much. Maybe a link to the articles you mentioned would be helpful Commented Jun 29, 2021 at 19:04
0

There are so many good answers here already, but i just want to show with an example that you can add additional functionality to the class being extended;

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

class Class1 {
    doWork() {
        console.log('Working');
    }
}

class Class2 {
    sleep() {
        console.log('Sleeping');
    }
}

class FatClass implements Class1, Class2 {
    doWork: () => void = () => { };
    sleep: () => void = () => { };


    x: number = 23;
    private _z: number = 80;

    get z(): number {
        return this._z;
    }

    set z(newZ) {
        this._z = newZ;
    }

    saySomething(y: string) {
        console.log(`Just saying ${y}...`);
    }
}
applyMixins(FatClass, [Class1, Class2]);


let fatClass = new FatClass();

fatClass.doWork();
fatClass.saySomething("nothing");
console.log(fatClass.x);
0

You can call Dynamic Inheritance or Class Factory.

type ClassConstructor<T> = {
  new (...args: any[]): T;
};

interface IA {
  a: string;
}

interface IB {
  b: string;
}

interface IAB extends IA, IB {}

class EmptyClass {}

function GetA<T>(t: ClassConstructor<T> = EmptyClass as any) {
  class A extends (t as any) implements IA {
    a = 'Default value a';
  }
  return A as unknown as ClassConstructor<IA & T>;
}

function GetB<T>(t: ClassConstructor<T> = EmptyClass as any) {
  class B extends (t as any) implements IB {
    b = 'Default value b';
  }
  return B as unknown as ClassConstructor<IB & T>;
}


class C extends GetA<IB>(GetB()) implements IAB {}
0

The following is an example I improved, but same private attributes cannot appear. I hope someone can improve it.

type MinixCons<T = {}, T2 = any> = {
  new(...arg: (T2 extends new (...arg2: any) => any ? ConstructorParameters<T2> : any)): T
}
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

function applyMixins<T extends MinixCons[] = any>(...conses: T): MinixCons<UnionToIntersection<{ [index in keyof T]: InstanceType<T[index]>; }[number]>, T[0]> {
  let constructorList: Function[] = [];
  class NewInstance {
    constructor(...arg) {
      constructorList.map((item) => {
        item.apply(this, arg)
      })
    }
  }
  conses.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      if (name !== 'constructor') {
        NewInstance.prototype[name] = baseCtor.prototype[name];
      } else {
        constructorList.push(baseCtor.prototype[name])
      }
    });
  });

  return NewInstance as any
}


class Point {
  o: string
  private _test() {
    console.log("private test")
  }
  constructor(id: number, str: string) {
    console.log('Point', arguments)
  }
  test() {
    console.log("test")
    this._test()
  }
}

class Point2 {
  constructor(id: number, str: string) {
    console.log('Point2', arguments)
  }
  test2() {
    console.log("test2")
  }
}

class Point3 {
  test3() {
    console.log("test3")
  }
}

class Point34 {
  test4() {
    console.log("test4")
  }
}
const NewPoint = applyMixins(Point, Point2, Point3, Point34);
let ok = new NewPoint(1, 'hello');
ok.test()
ok.test2()
ok.test3()
-1

Found a way:

export interface IsA {
    aWork(): void
}

export interface IsB {
   
}

export class A implements IsA {
    aWork() {   }
}

export class B implements IsB {
    bWork() {   }
}

export interface IsAB extends A, B {}
export class IsAB {}

Then you can

export class C extends IsAB {}

or even

const c = new IsAB {}

Not the answer you're looking for? Browse other questions tagged or ask your own question.