12

How can I achieve something similar to this pattern in typescript?

class A {
    Init(param1: number) {
        // some code
    }
}

class B extends A {
    Init(param1: number, param2: string) {
        // some more code
    }
}

The code snipped above appears like it should work, however on close inspection of How Typescript function overloading works it makes sense that an error is thrown:

TS2415: 'Class 'B' incorrectly extends base class 'A'. 
Types of property 'Init' are incompatible.

I know that constructor functions allow this behaviour, but I can't use constructors here as these objects are pooled for memory efficiency.

I could provide another definition of Init() in class A:

class A {
    Init(param1: number, param2: string): void;
    Init(param1: number) {
        // some code
    }
}

However this is less than ideal as now the base class needs to know about all of its derived classes.

A third option would be to rename the Init method in class B but that would not only be pretty ugly and confusing, but leave exposed the Init() method in the base class, which would cause difficult-to-detect bugs when the base class Init() is called by mistake.

Is there any way to implement this pattern that doesn't have the pitfalls of the aforementioned approaches?

2 Answers 2

12

TypeScript complains about methods not being interchangeable: what would happen if you do the following?

let a:A = new A(); // a is of type A
a.Init(1)
a = new B(); // a is still of type A, even if it contains B inside
a.Init(1) // second parameter is missing for B, but totally valid for A, will it explode?

If you don't need them to be interchangeable, modify B's signature to comply with A's:

class B extends A {
    Init(param1: number, param2?: string) { // param 2 is optional
        // some more code
    }
}

However, you might find yourself in a situation where you need to create a class with totally different method signature:

class C extends A {
    Init(param1: string) { // param 1 is now string instead of number
        // some more code
    }
}

In this case, add a list of method signatures that satisfy both current class and base class calls.

class C extends A {
    Init(param1: number)
    Init(param1: string)
    Init(param1: number | string) { // param 1 is now of type number | string (you can also use <any>)
        if (typeof param1 === "string") { // param 1 is now guaranteed to be string
            // some more code
        }
    }
}

That way the A class doesn't have to know about any of the derived classes. As a trade-off, you need to specify a list of signatures that satisfies both base class and sub class method calls.

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

4 Comments

Thanks for your response! I'd prefer not to have to do runtime type checking if I can avoid it though, Typescript manages to prevent that most everywhere else.
Unfortunately, you can't go without runtime checks if your subclass' methods signatures do not match base class ones. Take a look at my first example: someone may be calling your subclass as if it is actually base clase
Ah, all right, I think I understand now. Perhaps I will make both classes inherit their other common properties from an abstract base class.
a.Init(1) should definitively call A's init method because that's how OOP works. Init signature with 1 argument is inherited by A and init signature with 2 arguments is proper from B.
1

For someone who wants to extend a type. Basing on zlumer's answer, and using Intersection types

interface ConsumerGroup {
  on(message: 'message'): void
  on(message: 'error'): void
}

interface ConsumerGroup2 {
  on(message: 'messageDecoded'): void;
  on(message: 'rawMessage'): void;
}

// Intersection types
type ConsumerGroupEx = ConsumerGroup2 & ConsumerGroup;

function newEvent(): ConsumerGroupEx {
  return "just for test" as unknown as ConsumerGroupEx;
}

const evt = newEvent();

evt.on('messageDecoded'); // ok
evt.on('message'); // ok
evt.on('error'); // ok
evt.on('notExist'); // compilation error

1 Comment

Would the same method still work if ConsumerGroup2 was child of ConsumerGroup interface?

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.