打字稿:使用类作为接口 [英] Typescript: Use class as interface

查看:84
本文介绍了打字稿:使用类作为接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图将一个类用作另一个类的接口.为此,我正在尝试完成用于测试的最新模拟类.

这应该是可能的,并且是一种有利的方法,例如 https://angular.io/guide中的状态/styleguide#interfaces

并在此处进行演示:将类导出为Angular2中的接口

但是奇怪的是,我在VSCode 1.17.2中使用Typescript 2.5.3遇到错误

SpecialTest类中的错误

  [ts]类'SpecialTest'错误地实现了接口'Test'.类型具有私有属性名称"的单独声明. 

示例代码:

  class测试{私有名称:字符串;构造函数(名称:字符串){this.name =名称;}getName(){返回this.name;}setName(名称:字符串):void {this.name =名称;}}class SpecialTest实现Test {私有名称:字符串;getName():字符串{返回 '​​';}setName(名称:字符串):void {}} 

我想念什么?

使用 string 代替 String ,如@fenton建议

解决方案

在我们开始之前,扩展类时,请使用 extends 关键字.您扩展一个类,并实现一个接口.帖子的后面还有其他说明.

  class SpecialTest扩展了Test { 

另外,请注意 string String ,因为这会使您绊倒.您的类型批注几乎可以肯定是 string (小写).

最后,您无需手动分配构造函数参数,因此原始代码:

  class测试{私有名称:字符串;构造函数(名称:字符串){this.name =名称;}//...} 

最好表示为:

  class测试{构造函数(私有名称:字符串){}//...} 

现在,您可以从多种解决方案中进行选择.

受保护的成员

保护 name 成员,然后可以在子类中使用它:

  class测试{受保护的名称:字符串;构造函数(名称:字符串){this.name =名称;}getName(){返回this.name;}setName(名称:字符串):void {this.name =名称;}}class SpecialTest扩展了Test {getName():字符串{返回 '​​';}setName(名称:字符串):void {}} 

接口

这是我认为最符合您需求的解决方案.

如果将公共成员拉入接口​​,则应该能够将两个类都视为该接口(无论是否显式使用 implements 关键字-TypeScript是结构化类型).

  interface SomeTest {getName():字符串;setName(名称:字符串):void;} 

如果愿意,可以明确实现它:

  class SpecialTest实现SomeTest {私有名称:字符串;getName():字符串{返回 '​​';}setName(名称:字符串):void {}} 

您的代码现在可以依赖于接口而不是具体的类了.

使用类作为接口

从技术上讲,可以将类引用为接口,但是在使用 implements MyClass 进行此操作时会遇到一些问题.

首先,您为以后不得不阅读您的代码(包括将来的您)的任何人添加了不必要的复杂性.您还使用了一种模式,这意味着您需要注意关键字.意外使用 extends 可能在将来更改继承的类时引起棘手的错误.维护人员将需要就使用哪个关键字形成鹰眼.都是为了什么?要以结构性语言保留名义上的习惯.

接口是抽象的,并且不太可能更改.上课更具体,并且更有可能改变.将类用作接口会破坏依赖于稳定抽象的整个概念……而会导致您依赖于不稳定的具体类.

请考虑在整个程序中类作为接口"的泛滥.对类的更改(例如,我们添加了一个方法)可能会在无意中引起较大距离的波动……程序的多少部分现在拒绝输入,因为输入中不包含甚至没有使用过的方法?

更好的选择(当没有访问修饰符兼容性问题时)...

在类之外创建接口:

 接口MyInterface扩展了MyClass {} 

或者,仅在第二堂课中根本不引用原始课.允许结构类型系统检查兼容性.

旁注...根据您的TSLint配置,弱类型(例如仅具有可选类型的接口)将触发 no-empty-interface -Rule.

私人会员的特殊情况

这些方法(使用类作为接口,从类或结构类型生成接口)都不能解决私有成员的问题.这就是为什么解决实际问题的解决方案是创建一个与公共成员保持联系的界面.

在私人成员的特定情况下(例如在问题中),让我们考虑一下如果继续使用原始模式会发生什么情况?自然的结果是,为了保留使用类作为接口的模式,将更改成员的可见性,如下所示:

  class测试{公共名称:字符串;构造函数(名称:字符串){this.name =名称;}getName(){返回this.name;}setName(名称:字符串):void {this.name =名称;}} 

现在,我们正在打破更为成熟的面向对象原则.

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

解决方案

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.

这篇关于打字稿:使用类作为接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆