Typescript: How to extend two classes?

I want to save my time and to reuse common code across classes which extends 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 extends 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 an experienced TypeScript feedback/advices 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.

Answers:

Answer

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();
Answer

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();
Answer

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/

Answer

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
Answer

There is a new feature in JavaScript (ES7) called 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;
}
Answer

I found an up-to-date & unparalleled solution: https://www.npmjs.com/package/ts-mixer

Answer

I found an up-to-date & unparalleled solution: https://www.npmjs.com/package/ts-mixer

Answer

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'

Answer

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

In object oriented programming there is a principle named "Composition over inheritance". It says instead of inheriting from classes,you can create instances of them and use instances inside your class. You can see some examples of that here and here.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.