9

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.

3

3 Answers 3

7

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.

Sign up to request clarification or add additional context in comments.

1 Comment

what about the case where the number of arguments is different (e.g 1 and 3)
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
    }
}

1 Comment

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?
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;
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.