对 TypeScript 的接口和 Class 编码指南感到困惑

Confused about the Interface and Class coding guidelines for TypeScript

我通读了TypeScript Coding guidelines

我发现这个说法相当令人费解:

Do not use "I" as a prefix for interface names

我的意思是,如果没有 "I" 前缀

,这样的事情就没有多大意义
class Engine implements IEngine

我是不是遗漏了什么明显的东西?

我不太明白的另一件事是:

Classes

For consistency, do not use classes in the core compiler pipeline. Use function closures instead.

这是否说明我根本不应该使用 类?

希望有人能帮我解决:)

关于您引用的 link 的说明:

This is the documentation about the style of the code for TypeScript, and not a style guideline for how to implement your project.

如果使用 I 前缀对您和您的团队有意义,请使用它(我这样做)。

如果没有,也许 SomeThing(接口)的 Java 风格和 SomeThingImpl(实现)然后一定要使用它。

当 team/company 发布 framework/compiler/tool-set 时,他们已经有了一些经验和最佳实践。他们将其作为指南分享。准则是建议。如果你不喜欢任何一个,你可以忽略它们。 编译器仍然会编译 你的 代码。 虽然when in Rome...

这就是我的看法,为什么 TypeScript 团队不建议使用 I-前缀接口。

原因 #1 Hungarian notation 的时代已经过去了

I-prefix-for-interface 支持者的主要论点是前缀有助于立即理解(查看)类型是否是接口。前缀有助于立即 grokking(偷看)的声明是对 Hungarian notation 的呼吁。 I接口名称前缀,C表示class,A表示抽象class,s表示字符串变量,c表示const 变量,i 表示整型变量。我同意这样的名称装饰可以为您提供类型信息,而无需将鼠标悬停在标识符上或通过 hot-key 导航到类型定义。这个微小的好处被 Hungarian notation 缺点和下面提到的其他原因所抵消。现代框架中不使用匈牙利符号。由于历史原因 (COM),C# 的接口有 I 前缀(这是 C# 中唯一的前缀)。回想起来,一位 .NET 架构师 (Brad Abrams) 认为 不使用 I 前缀 会更好。 TypeScript 是 COM-legacy-free 因此它没有 I-prefix-for-interface 规则。

原因#2 I-前缀违反封装原则

假设您得到了一些 black-box。您将获得一些类型引用,允许您与该框进行交互。你不应该关心它是一个接口还是一个 class。您只需使用它的接口部分。要求知道它是什么(接口,具体实现或抽象class)是违反封装的。

示例:假设您需要在代码中修复 API Design Myth: Interface as Contract,例如删除 ICar 接口并使用 Car base-class 代替。然后,您需要在所有消费者中执行此类替换。 I-prefix 导致消费者对 black-box 实现细节的隐式依赖。

原因 #3 防止错误命名

开发者懒得好好想名字。命名是Two Hard Things in Computer Science之一。当开发人员需要提取接口时,只需将字母 I 添加到 class 名称即可轻松获得接口名称。不允许接口使用 I 前缀会迫使开发人员绞尽脑汁为接口选择合适的名称。选择的名称不仅要在前缀上有所不同,而且要强调意图差异。

抽象案例:您应该不定义ICar接口和关联的Carclass。 Car 是一种抽象,它应该是用于合约的。实现应具有描述性的、独特的名称,例如SportsCar, SuvCar, HollowCar.

很好的例子:WpfeServerAutosuggestManager implements AutosuggestManager, FileBasedAutosuggestManager implements AutosuggestManager.

错误示例:AutosuggestManager implements IAutosuggestManager.

原因 #4 正确选择名字可以预防 API Design Myth: Interface as Contract

在我的实践中,我遇到很多人在具有 Car implements ICar 命名方案的单独界面中不加考虑地复制 class 的界面部分。在单独的接口类型中复制 class 的接口部分不会神奇地将其转换为抽象。您仍将获得具体的实现,但具有重复的接口部分。如果你的抽象不是很好,复制接口部分无论如何都不会改进它。提取抽象是一项艰苦的工作。

注意:在 TS 中,您不需要单独的接口来模拟 classes 或重载功能。 您可以使用 TypeScript utility types,而不是创建一个单独的接口来描述 class 的 public 成员。例如。 Required<T> 构造一个类型,该类型由 T.

类型的所有 public 个成员组成
export class SecurityPrincipalStub implements Required<SecurityPrincipal> {
  public isFeatureEnabled(entitlement: Entitlement): boolean {
      return true;
  }
  public isWidgetEnabled(kind: string): boolean {
      return true;
  }

  public areAdminToolsEnabled(): boolean {
      return true;
  }
}

如果你想构造一个不包括一些 public 成员的类型,那么你可以使用 combination of Omit and Exclude.

作为接口的类型是一个实现细节。实施细节应隐藏在 API:s 中。这就是为什么你应该避免 I.

你应该避免prefixsuffix。这些都是错误的:

  • ICar
  • CarInterface

你应该做的是在 API 中使一个漂亮的名字可见,并在实现中隐藏一个实现细节。这就是为什么我建议:

  • Car - 在 API.
  • 中公开的接口
  • CarImpl - API 的实现,对消费者隐藏。

我发现 @stanislav-berkov 是对 OP 问题的一个很好的回答。我只会分享我的 2 美分补充说,最终取决于你的 Team/Department/Company/Whatever 达成共识并设置自己的 rules/guidelines 来跟进。

只要可能和需要,坚持标准 and/or 约定是一种很好的做法,它可以使事情更容易理解。另一方面,我确实认为我们仍然可以自由选择编写代码的方式。

从情感方面考虑一下,我们编写代码的方式或我们的编码风格反映了我们的个性,在某些情况下甚至反映了我们的心情。这就是让我们人类而不只是编码机器遵守规则的原因。 我相信编码可以成为一门手艺,而不仅仅是工业化的过程。

我个人非常喜欢通过添加 -able 后缀将名词变成形容词的想法。听起来很不合适,但我喜欢它!

interface Walletable {
  inPocket:boolean
  cash:number
}


export class Wallet implements Walletable {
  //...
}

}

我正在尝试与其他答案类似的模式,但导出一个将具体 class 实例化为接口类型的函数,如下所示:

export interface Engine {
  rpm: number;
}

class EngineImpl implements Engine {
  constructor() {
    this.rpm = 0;
  }
}

export const createEngine = (): Engine => new EngineImpl();

在这种情况下,永远不会导出具体实现。

我想添加一个 Props 后缀。

interface FormProps {
 some: string;
}

const Form:VFC<FormProps> = (props) => {
  ...
}

Typescript 文档中建议的指南不是针对使用 typescript 的人,而是针对为 typescript 项目做出贡献的人。如果您阅读页面开头的详细信息,它会清楚地定义谁应该使用该指南。这是指南的 link。 Typescript guidelines

总而言之,作为开发人员,您可以按照自己认为合适的方式命名界面。