7

I want to overload an abstract method within an abstract class like this:

abstract class Animal {

    public abstract communicate(sentence: string): void;
    public abstract communicate(notes: string[]): void;

}

class Human extends Animal {

    public communicate(sentence: string): void {
        // Do stuff
    }

}

class Bird extends Animal {

    public communicate(notes: string[]): void {
        // Do stuff
    }

}

However, Typescript gives an error, stating I incorrectly extend the base class (Animal)

Is there something I am doing wrong? Like expecting something to work which would work anyway according to OOP? Or is it something Typescript doesn't support?

Note: The type of the parameter can be entirely different types unlike in this example.

Wilco Bakker
  • 527
  • 1
  • 7
  • 18
  • Check out this question http://stackoverflow.com/questions/12688275/method-overloading. Looks like when Typescript transpiles to JS, having two methods with the same signature is invalid, even if they have different params. – zigzag May 15 '17 at 13:58
  • It might be useful to see what TypeScript produces as output js code - http://www.typescriptlang.org/play/ – Bakudan May 15 '17 at 13:59
  • try this link http://stackoverflow.com/questions/13212625/typescript-function-overloading#13212871 – Shmulik K May 15 '17 at 14:20

3 Answers3

4

Like @DavidSherret said the sub-class must implement all the abstract class overloads, not just one of them. So you won't really be able to do what you want using abstract class overloads.

Since what you want is to have Bird and Human have a different communicate() param type, enforced by the compiler, I would use a generic type argument with type constraint:

abstract class Animal<C extends string | string[]> {
    public abstract communicate(communication: C): void;
}

class Human extends Animal<string> {
    public communicate(sentence: string): void { }
}

class Bird extends Animal<string[]> {
    public communicate(notes: string[]): void { }
}

class Bynar extends Animal<boolean> { // Error: 'boolean' does not satisfy the constraint 'string | string[]'.
    public communicate(bit: boolean): void { }
}

const human = new Human();
human.communicate("hello"); // OK
human.communicate(["hello"]); // Error

const bird = new Bird();
bird.communicate("hello"); // Error
bird.communicate(["hello"]); // OK

Tip: you can make use of TS 2.3 default type arguments here as well.

Aaron Beall
  • 49,769
  • 26
  • 85
  • 103
3

That abstract class expects two method signatures to be implemented. Those two method signatures being:

public abstract communicate(sentence: string): void;
public abstract communicate(notes: string[]): void;

They can be implemented like so:

class Human extends Animal {
    communicate(sentence: string); // optional overload signature
    communicate(notes: string[]);  // optional overload signature
    communicate(sentenceOrNotes: string | string[]) {
        // Do stuff
    }
}

The final signature there is the implementation signature. It needs to be compatible with the method signatures that need to be implemented.

Note on Child Classes

Child classes need to be compatible with the base so that when you do...

const animal: Animal = new Bird();

...the child class should be able to handle both calls:

animal.communicate("some sentence");
animal.communicate(["notes", "more notes"]);

In this case it might be more appropriate to create separate interfaces based on the form of communication (or use mixins):

interface CommunicatesWithSentences {
    communicate(sentence: string);
}

class Human extends Animal implements CommunicatesWithSentences {
    communicate(sentence: string) {
        // Do stuff
    }
}
David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • 1
    The problem with this is, that in the child class, both methods are still valid, of course I could throw an error if one is used I do not expect however I don't think that is a very pretty solution as it can still be called. Is there a way to achieve that? – Wilco Bakker May 15 '17 at 14:34
0

you can do it like this, I hope this is useful for you guys.

export abstract class BaseComponent {

     constructor(protected valueName: string) {

     }

     protected doSomethingMoreButOptional(config: IConfig): void { }

     protected doSomething() {
         if (valueName === 'derived') {
             this.doSomethingMoreButOptional(new Config());
         }
     }
}

export class DerivedComponent extends BaseComponent {

    private _xxx: any[];
    private _yyy: any[];

    constructor() {
        super('derived');
        super.doSomething();
    }

    protected doSomethingMoreButOptional(config: IConfig): void {
        switch (config.ABC) {
           case 'xxx':
              config.options = this._xxx;
              break;

           case 'yyy':
              config.options = this._yyy;
              break;

           default:
              return;
        }
    }
}
Triet Nguyen
  • 763
  • 9
  • 20