it-efrem
it-efrem

Reputation: 107

The problem with interfaces that extends the interface

I want classes that implement the Observer interface to also implement the Comparable interface, how can I do this?

interface Comparable<T> {
    equals: (item: T) => boolean;
}

interface Observer extends Comparable<Observer> {
    notify: () => void
}

class TestA implements Observer {
    private name = '';

    equals = (item: TestA) => {
        return this.name === item.name
    }

    notify = () => {}
}

class TestB implements Observer {
    private name = '';

    equals = (item: TestB) => {
        return this.name === item.name
    }

    notify = () => {}
}

Error:

TS2416: Property 'equals' in type 'TestA' is not assignable to the same property in base type 'Observer'.   Type '(item: TestA) => boolean' is not assignable to type '(item: Observer) => boolean'.     Types of parameters 'item' and 'item' are incompatible.       Property 'name' is missing in type 'Observer' but required in type 'TestA'.

But, TestA implements the Observer interface why are they not compatible?

Of course, I can write like this:

class TestA implements Observer {
    private name = '';

    equals = (item: Observer) => {
        return this.name === item.name
    }

    notify = () => {}
}

But then I get such an error, and besides, this is not entirely correct, because I want to compare only objects of this class:

Property 'name' does not exist on type 'Observer'.

How to do it right? "typescript": "^3.9.2"

Upvotes: 0

Views: 403

Answers (2)

jcalz
jcalz

Reputation: 328152

Have you considered using polymorphic this instead of generics? Your Comparable and Observer would become:

interface Comparable {
    equals: (item: this) => boolean;
}

interface Observer extends Comparable {
    notify: () => void
}

meaning that object of type X that extends Comparable needs to have an equals() method taking a value of type X. Note that this implies that Comparable doesn't act like a normal type in terms of substitutability and inheritance. Generally if you have interface B extends A {...} then you should be able to use a B anywhere you require an A:

interface A {
    someMethod(x: A): void;
}

interface B extends A {
    someOtherMethod(x: B): void;
}

declare const b: B;
const a: A = b; // okay

But this will not work for Comparable:

declare const o: Observer;
const c: Comparable = o; // error! equals is incompatible

Anyway, this definition of Comparable will allow your implementations as-is:

class TestA implements Observer {
    private name = '';

    equals = (item: TestA) => {
        return this.name === item.name
    }

    notify = () => { }
}

class TestB implements Observer {
    private name = '';

    equals = (item: TestB) => {
        return this.name === item.name
    }

    notify = () => { }
}

But again, you'll run into issues if you try to treat TestA or TestB as an Observer:

function takeObservers(o1: Observer, o2: Observer) {
    o1.equals(o2);    
}
takeObservers(new TestA()); // error

So you might decide that you actually don't want to constrain equals() this way.


Okay, hope that helps; good luck!

Playground link to code

Upvotes: 2

Jayanth
Jayanth

Reputation: 6277

Change equals(item: TestA) and equals(item:TestB) to equals(item : Observer) in TestA class and TestB class.

Since Comparable type was given as Observable.

And in equals body you can cast observable object to TestA and compare its name property like below.

in TestA class.

class TestA implements Observer {
    private name = '';

    equals = (item: Observer) => {
        if(item instanceof TestA){
          return this.name === (item as TestA).name
        }
        return false
    }

    notify = () => {}
}

Upvotes: 0

Related Questions