问题
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:
- why do private members even matter - if I implement the same algorithm using different data structures, will I have to declare something named the same just for the sake of type checking?
- why do I get the error when implementing
f
as a private function?
I wouldn't do this in practice, but I am curious about why TS works like this.
Thanks!
回答1:
The issue Microsoft/TypeScript#18499 discusses why private members are required when determining compatibility. 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 ofIdentity
but attempting to use it as one will crash insameAs
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!
回答2:
In this case it is not possible with the current typescript specifications. There is a tracked issue for this but it is closed.
Suggestion: Permit an implementing class to ignore private methods of the implementee class.
See also Extending vs. implementing a pure abstract class in TypeScript
回答3:
This is fundamentally due to the fact the visibility of private members are scoped to the type, and not the instance. Meaning that all objects of the type T have access to the privates of other objects of type T.
This is not a problem in nominatively typed languages as all instances of T inherits the implementation of T, but since typescript is structurally typed, it mean that we can not assume that all instances that fulfill T have the implementation of the class that declares type T.
This means that privately scoped members have to be a part of the public contract of the type, otherwise an object of the structural type T could call a non-existing private member of another object with the same structural type.
Being forced to have privates being a part of a public type contract is bad, and could have been avoided by scoping privates to the instance and not the type.
回答4:
The current solution with out-of-the-box support from Typescript is simply
class A {
private f() { console.log("f"); }
public g() { console.log("G"); }
}
class B implements Pick<A, keyof A> {
public g() { console.log("g"); }
}
Explanation: keyof A
only returns public properties (and methods) of A
, and Pick
will then down trim A
to only its public properties and their respective type.
来源:https://stackoverflow.com/questions/48953587/typescript-class-implements-class-with-private-functions