打字稿:使用 class 作为界面

Typescript: Use class as interface

我尝试使用 Class 作为另一个 class 的界面。有了这个,我正在尝试完成一个最新的模拟Class 用于测试。

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

中所述

并在此处演示:

但奇怪的是,我在 VSCode 1.17.2

中使用 Typescript 2.5.3 时遇到错误

class SpecialTest

错误
[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
  Types have separate declarations of a private property 'name'.

示例代码:

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 {
    }
}

我错过了什么?

编辑: 使用 string 而不是 String ,正如@fenton 所建议的

在您链接到 的 post 中 你在实施中做了最后一部分吗:

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 }]
 ...

中,父 class 应该是抽象的并充当接口。 TypeScript 接口没有访问修饰符,所有接口属性都应为 public.

当像 Test 这样的具体 class 用作接口时出现问题,因为实现无法实现私有属性,也无法覆盖它们。

TestSpecialTest 都应该实现一些抽象 class 作为接口(在这种情况下,抽象 class 只能有 public 成员)或者从它继承(在这种情况下抽象 class 可以有 protected 个成员)。

在我们开始之前,当您扩展 class 时,您会使用 extends 关键字。您扩展了一个 class,并实现了一个接口。 post.

下方还有关于此的附加注释
class SpecialTest extends Test {

此外,请注意 stringString,因为这会让您失望。您的类型注释几乎肯定应该是 string(小写)。

最后,不需要手动给构造器参数赋值,所以原文:

class Test {
    private name: string;

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

    // ...
}

更好地表示为:

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

    // ...
}

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

受保护的成员

保护 name 成员,然后你可以在 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 {
    }
}

界面

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

如果您将 public 成员拉入一个接口,您应该能够将两个 class 都视为该接口(无论您是否显式使用 implements 关键字 - TypeScript 是结构类型的。

interface SomeTest {
  getName(): string;
  setName(name: string): void;
}

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

class SpecialTest implements SomeTest {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

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

使用 Class 作为接口

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

首先,您为以后必须阅读您的代码的任何人(包括未来的您)增加了不必要的复杂性。您还使用了一种模式,这意味着您需要注意关键字。意外使用 extends 可能会在将来更改继承的 class 时导致棘手的错误。维护者需要对使用哪个关键字保持鹰眼。一切为了什么?在结构语言中保留名义上的习惯。

界面是抽象的,不太可能改变。 Class这些更具体,也更有可能发生变化。使用 class 作为接口破坏了依赖稳定抽象的整个概念......反而导致你依赖不稳定的具体 classes.

考虑整个程序中 "classes as interfaces" 的扩散。对 class 的更改(假设我们添加了一个方法)可能会无意中导致变化在很远的距离内波动...现在程序的多少部分拒绝输入,因为输入不包含一个方法连用都没有?

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

从 class:

创建接口
interface MyInterface extends MyClass {
}

或者,在您的第二个 class 中完全不引用原始 class。允许结构类型系统检查兼容性。

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

私有成员的具体案例

其中

None(使用 class 作为接口,从 class 或结构类型生成接口)解决了私有成员的问题。这就是为什么解决实际问题的解决方案是创建一个带有 public 成员的接口。

在private成员的具体情况下,比如问题中,我们想一想,如果按照原来的模式继续下去会怎么样?自然的结果是,为了保留使用 class 作为接口的模式,成员的可见性将被更改,如下所示:

class Test {
    public name: string;

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

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

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

跟进@Fenton 在使用Class作为接口

中的解释

使用 InstanceType<T> 实用程序类型可以更明确地说明 class SpecialTest implements Test。例如class SpecialTest implements InstanceType<typeof Test>。我发现阅读代码时不那么混乱了。

在你自己的代码中更喜欢接口,这个魔法 我只在我需要为第 3 方创建装饰器时发现 useful/necessary class.

要从 class 中提取一个接口,它只包含 public 属性而不是 class 的所有实现细节,您可以使用 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.

[...] keyof T, the index type query operator. For any type T, keyof T is the union of known, public property names of T.