typescriptimplements

TypeScript class implements class with private functions


I was exploring the possibility of having a class implementing a class in TypeScript.

Hence, I wrote the following code Playground link:

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

class B implements A {
    public g() { console.log("g"); }
}

And I got the error: Class 'B' incorrectly implements class 'A' --- property 'f' is missing in type 'B' coupled with a suggestion that I actually meant extends.

So I tried to make a private field called f (public didn't work as it detects they have different access modifiers) Playground link

Now I get the error: Class 'B' incorrectly implements class 'A'. Types have separate declarations of a private property 'f'; this leaves me very confused:

I wouldn't do this in practice, but I am curious about why TS works like this.

Thanks!


Solution

  • The issue Microsoft/TypeScript#18499 discusses why private members are required when determining compatibility. The reason is: class private members are visible to other instances of the same class.

    One remark by @RyanCavanaugh is particularly relevant and illuminating:

    Allowing the private fields to be missing would be an enormous problem, not some trivial soundness issue. Consider this code:

    class Identity { private id: string = "secret agent"; public sameAs(other: Identity) { return this.id.toLowerCase() === other.id.toLowerCase(); } } class MockIdentity implements Identity { public sameAs(other: Identity) { return false; } }
    MockIdentity is a public-compatible version of Identity but attempting to use it as one will crash in sameAs when a non-mocked copy interacts with a mocked copy.

    Just to be clear, here's where it would fail:

    const identity = new Identity();
    const mockIdentity = new MockIdentity();
    identity.sameAs(mockIdentity); // boom!
    

    So, there are good reasons why you can't do it.


    As a workaround, you can pull out just the public properties of a class with a mapped type like this:

    type PublicPart<T> = {[K in keyof T]: T[K]}
    

    And then you can have B implement not A but PublicPart<A>:

    class A {
        private f() { console.log("f"); }
        public g() { console.log("G"); }
    }
    
    // works    
    class B implements PublicPart<A> {
        public g() { console.log("g"); }
    }
    

    Hope that helps; good luck!