问题
I tried to use an Class as Interface for another class. With this I am trying to accomplish an up-to-date mockClass for Testing.
This should be possible and is an favorable approach, as state in https://angular.io/guide/styleguide#interfaces
And is demonstrate here: Export class as interface in Angular2
But strangely I'm getting an Error using Typescript 2.5.3 in VSCode 1.17.2
Error in class SpecialTest
[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
Types have separate declarations of a private property 'name'.
Samplecode:
class Test {
private name: string;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
class SpecialTest implements Test {
private name: string;
getName(): string {
return '';
}
setName(name: string): void {
}
}
What am I missing?
EDIT:
use string
instead of String
, as @fenton suggested
回答1:
Before we get started, when you extend a class, you use the extends
keyword. You extend a class, and implement an interface. There are additional notes on this further down the post.
class SpecialTest extends Test {
Also, watch out for string
vs String
as this will trip you up. Your type annotations should almost certainly be string
(lowercase).
And finally, you don't need to manually assign constructor parameters, so the original:
class Test {
private name: string;
constructor(name: string) {
this.name = name;
}
// ...
}
Is better expressed as:
class Test {
constructor(private name: string) {
}
// ...
}
Now you can choose from a number of solutions to your problem.
Protected Member
Make the name
member protected, then you can use it within sub classes:
class Test {
protected name: string;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
class SpecialTest extends Test {
getName(): string {
return '';
}
setName(name: string): void {
}
}
Interface
This is the solution that I think best matches your needs.
If you pull the public members into an interface, you should be able to treat both classes as that interface (whether or not you explicitly use the implements
keyword - TypeScript is structurally typed).
interface SomeTest {
getName(): string;
setName(name: string): void;
}
You could explicitly implement it if you wish:
class SpecialTest implements SomeTest {
private name: string;
getName(): string {
return '';
}
setName(name: string): void {
}
}
Your code can now depend on the interface rather than on a concrete class.
Using a Class as an Interface
It is technically possible to reference a class as an interface, but there are problems ahead of you do this with implements MyClass
.
Firstly, you are adding unnecessary complexity for anyone who has to read your code later, including the future you. You are also using a pattern that means you need to be careful about the keyword. Accidental use of extends
may cause a tricky bug in the future when the inherited class is changed. Maintainers will need to become hawkeye over which keyword is used. And all for what? To preserve nominal habits in a structural language.
Interfaces are abstract, and less likely to change. Classes are more concrete, and more likely to change. Using a class as an interface ruins the entire concept of depending on stable abstractions... and instead causes you to depend on unstable concrete classes.
Consider the proliferation of "classes as interfaces" throughout a program. A change to a class (let's say we add a method) can inadvertently cause a change to ripple for vast distances... how many parts of the program now reject input, because the input doesn't contain a method that is not even used?
The better alternatives (when there are no access modifier compatibility issues)...
Create an interface out of the class:
interface MyInterface extends MyClass {
}
Or, just don't reference the original class at all in your second class. Allow the structural type system to check compatibility.
Side note... depending on your TSLint config, weak types (such as an interface with only optional types) will trigger the no-empty-interface
-Rule.
Specific Case of Private Members
None of these (using a class as an interface, generating an interface from a class, or structural type) solve the problem of the private member. This is why the solution that solves the real problem is to create an interface with the public members on.
In the specific case of private members, such as in the question, let's think about what will happen if we continue with the original pattern? The natural outcome will be that to preserve the pattern of using the class as an interface, the visibility of members will be changed, like this:
class Test {
public name: string;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
And now we are breaking the more established principles of object-orientation.
回答2:
In this example parent class is supposed to be abstract and acts as an interface. TypeScript interfaces don't have access modifiers, all interface properties are expected to be public.
When concrete class like Test
is used as interface a problem appears, because the implementation cannot implement private properties and cannot override them either.
Both Test
and SpecialTest
should either implement some abstract class as interface (in this case abstract class can have only public members) or inherit from it (in this case abstract class can have protected
members).
回答3:
To extract an interface from a class, that will only contain the public properties and not all the implementation details of the class you can use the keyof
operator together with the Pick<Type, Keys>
utilities.
class Test {
foo: any;
private bar: any;
}
class SepcialTest implements Pick<Test, keyof Test> {
foo: any;
}
Pick<Type, Keys> Constructs a type by picking the set of properties Keys from Type.
[...]
keyof T
, the index type query operator. For any type T, keyof T is the union of known, public property names of T.
回答4:
To follow up @Fenton explanation in Using a Class as an Interface
There is more explicit way to state class SpecialTest implements Test
by using InstanceType<T> utility type. e.g. class SpecialTest implements InstanceType<typeof Test>
. I find it less confusing when reading code.
Prefer interfaces in your own code, this magic I've only found useful/necessary when I needed to create a decorator for a 3rd-party class.
回答5:
in the post you linked to this did you do the last part of it in your implementation:
It should be noticed that any class can be used as an interface in >TypeScript. So if there's no real need to differentiate between interface >and implementation, it may be just one concrete class:
export class Foo { bar() { ... } baz() { ... } } ... provider: [Foo] ...
Which may be used later as an interface if necessary:
export class SomeFoo implements Foo { ... } ... provider: [{ provide: Foo, useClass: SomeFoo }] ...
来源:https://stackoverflow.com/questions/47114181/typescript-use-class-as-interface