简体   繁体   English

打字稿:使用类作为接口

[英]Typescript: Use class as interface

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这应该是可能的,并且是一种有利的方法,如https://angular.io/guide/styleguide#interfaces 中所述

And is demonstrate here: Export class as interface in Angular2并在这里演示: Export class as interface in Angular2

But strangely I'm getting an Error using Typescript 2.5.3 in VSCode 1.17.2但奇怪的是,我在 VSCode 1.17.2 中使用 Typescript 2.5.3 时遇到错误

Error in class SpecialTest 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编辑:使用string而不是String ,正如@fenton 建议的那样

Before we get started, when you extend a class, you use the extends keyword.在我们开始之前,当你扩展一个类时,你使用extends关键字。 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.另外,请注意stringString因为这会绊倒您。 Your type annotations should almost certainly be string (lowercase).您的类型注释几乎肯定应该是string (小写)。

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:使name成员受保护,然后您可以在子类中使用它:

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).如果您将公共成员拉入一个接口,您应该能够将两个类都视为该接口(无论您是否显式使用了implements关键字——TypeScript 是结构类型化的)。

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 .在技​​术上可以将类引用为接口,但是在使用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.当继承的类发生变化时,意外使用extends可能会导致将来出现棘手的错误。 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.旁注...根据您的 TSLint 配置,弱类型(例如只有可选类型的接口)将触发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.现在我们正在打破更加成熟的面向对象原则。

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.要从类中提取接口,该接口仅包含公共属性而不包含类的所有实现细节,您可以将keyof运算符与Pick<Type, Keys>实用程序一起使用。

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. Pick<Type, Keys>通过从 Type 中Pick<Type, Keys>一组属性 Keys 来构造一个类型。

[...] keyof T , the index type query operator. [...] keyof T ,索引类型查询运算符。 For any type T, keyof T is the union of known, public property names of T.对于任何类型 T,keyof T 是 T 的已知公共属性名称的并集。

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. TypeScript 接口没有访问修饰符,所有接口属性都应该是公共的。

When concrete class like Test is used as interface a problem appears, because the implementation cannot implement private properties and cannot override them either.当像Test这样的具体类用作接口时会出现问题,因为实现不能实现私有属性,也不能覆盖它们。

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). TestSpecialTest都应该实现一些抽象类作为接口(在这种情况下抽象类只能有公共成员)或从它继承(在这种情况下抽象类可以有protected成员)。

To follow up @Fenton explanation in Using a Class as an Interface跟进使用类作为接口中的@Fenton 解释

There is more explicit way to state class SpecialTest implements Test by using InstanceType<T> utility type.有更明确的方式来声明class SpecialTest implements Test通过使用InstanceType<T>实用程序类型class SpecialTest implements Test eg class SpecialTest implements InstanceType<typeof Test> .例如, 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.更喜欢你自己代码中的接口,这个魔法我只在我需要为 3rd 方类创建装饰器时才发现有用/必要。

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.需要注意的是,任何类都可以用作 >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 }] ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM