打字稿:使用类作为接口

jvo*_*igt 9 typescript typescript2.0 angular

我试图将Class作为接口用于另一个类.有了这个,我试图完成一个最新的mockClass测试.

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

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

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

类SpecialTest中的错误

[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
  Types have separate declarations of a private property 'name'.
Run Code Online (Sandbox Code Playgroud)

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 {
    }
}
Run Code Online (Sandbox Code Playgroud)

我错过了什么?

编辑:使用string而不是String像@fenton建议的那样

han*_*aad 8

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

class Test {
  foo: any;
  private bar: any;
}

class SepcialTest implements Pick<Test, keyof Test> {
  foo: any;
}
Run Code Online (Sandbox Code Playgroud)

Pick<Type, Keys> 通过从 Type 中选取一组属性 Keys 来构造一个类型。

[...] keyof T,索引类型查询运算符。对于任何类型 T,keyof T 是 T 的已知公共属性名称的并集。


Fen*_*ton 6

在我们开始之前,当您扩展一个类时,您使用extends关键字.您扩展了一个类,并实现了一个接口.在帖子上还有其他注释.

class SpecialTest extends Test {
Run Code Online (Sandbox Code Playgroud)

另外,请注意stringvs,String因为这会让你失望.你的类型注释几乎肯定是string(小写).

最后,您不需要手动分配构造函数参数,因此原始:

class Test {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

更好地表达为:

class Test {
    constructor(private name: string) {
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以从众多问题的解决方案中进行选择.

受保护的会员

使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 {
    }
}
Run Code Online (Sandbox Code Playgroud)

接口

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

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

interface SomeTest {
  getName(): string;
  setName(name: string): void;
}
Run Code Online (Sandbox Code Playgroud)

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

class SpecialTest implements SomeTest {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}
Run Code Online (Sandbox Code Playgroud)

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

使用类作为接口

技术上可以将类作为接口引用,但是在执行此操作之前存在问题implements MyClass.

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

接口是抽象的,不太可能改变.类更具体,更容易改变.使用类作为接口破坏了依赖于稳定抽象的整个概念......而是使您依赖于不稳定的具体类.

考虑整个程序中"类作为接口"的扩散.对类的更改(假设我们添加了一个方法)可能会无意中导致更改波动到很远的距离...程序的多少部分现在拒绝输入,因为输入不包含甚至不使用的方法?

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

在类外创建一个接口:

interface MyInterface extends MyClass {
}
Run Code Online (Sandbox Code Playgroud)

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

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

私人会员的具体案例

这些(使用类作为接口,从类生成接口或结构类型)都不能解决私有成员的问题.这就是为什么解决真正问题的解决方案是创建与公共成员的接口.

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

class Test {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}
Run Code Online (Sandbox Code Playgroud)

而现在我们正在打破更为既定的面向对象原则.