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

yxyvkwin  于 2023-03-31  发布在  TypeScript
关注(0)|答案(9)|浏览(144)

我读了TypeScript Coding guidelines
我发现这句话相当令人困惑:
不要使用“I”作为接口名称的前缀
我是说如果没有“我”这个前缀的话这种事就没什么意义了

class Engine implements IEngine

我错过了什么明显的东西吗?
另一件我不太明白的事是:

班级

为了保持一致性,不要在核心编译器管道中使用类,而要使用函数闭包。
这是否说明我根本不应该使用类?
希望有人能帮我澄清一下:)

pepwfjgg

pepwfjgg1#

当一个团队/公司发布一个框架/编译器/工具集时,他们已经有了一些经验,一套最佳实践。他们将其作为指导方针进行分享。指导方针是 * 建议 *。如果你不喜欢任何一个,你可以忽略它们。编译器仍然会编译你的代码。尽管when in Rome...
这就是为什么TypeScript团队不推荐I前缀接口的原因。

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

I-prefix-for-interface支持者的主要论点是,前缀有助于立即查找(窥视)类型是否是接口。前缀有助于立即查找的语句(peeking)是对Hungarian notation的呼吁。I前缀用于接口名称,C用于类,A用于抽象类,s用于字符串变量,c为常量变量,i表示整数变量。我同意这样的名称修饰可以提供类型信息,而无需将鼠标悬停在标识符上或通过热键导航到类型定义。这个微小的好处被Hungarian notation的缺点和下面提到的其他原因所抵消。匈牙利符号在当代框架中不使用。C#有I前缀由于历史原因(COM),这是C#中唯一的前缀。回想起来,.NET架构师之一(布拉德Abrams)认为not using I prefix会更好。TypeScript是COM遗留的,因此它没有I-prefix-for-interface规则。

原因#2 I-prefix违反封装原则

让我们假设你得到了一些黑盒。你得到了一些类型引用,允许你与那个盒子进行交互。你不应该关心它是一个接口还是一个类。你只需要使用它的接口部分。要求知道它是什么(接口,特定实现或抽象类)是违反封装的。
示例:假设你需要在代码中修复API Design Myth: Interface as Contract,例如删除ICar接口并使用Car基类。然后你需要在所有消费者中执行这样的替换。I-前缀导致消费者对黑盒实现细节的隐式依赖。

原因#3防止命名错误

开发人员懒得考虑名称。命名是Two Hard Things in Computer Science之一。当开发人员需要提取接口时,很容易将字母I添加到类名中,然后获得接口名称。禁止接口的I前缀迫使开发人员绞尽脑汁为接口选择合适的名称。所选的名字不仅要有前缀的不同,还要强调意图的不同。
抽象用例:你应该不定义ICar接口和相关的Car类。Car是一个抽象,它应该是用于合约的抽象。实现应该有描述性的,独特的名称,例如SportsCar, SuvCar, HollowCar
好例子:WpfeServerAutosuggestManager implements AutosuggestManagerFileBasedAutosuggestManager implements AutosuggestManager
不好的例子:AutosuggestManager implements IAutosuggestManager .

理由#4正确选择的名字可以让你预防API Design Myth: Interface as Contract

在我的实践中,我遇到了很多人,他们在一个具有Car implements ICar命名方案的单独接口中轻率地复制了一个类的接口部分。在单独的接口类型中复制一个类的接口部分并不能神奇地将其转换为抽象。你仍然会得到具体的实现,但有复制的接口部分。如果你的抽象不是那么好,复制接口部分无论如何也不会改善它。提取抽象是一项艰苦的工作。
注意:在TS中,你不需要单独的接口来模拟类或重载功能。而不是创建一个单独的接口来描述类的公共成员,你可以使用TypeScript utility types。例如,Required<T>构造了一个由T类型的所有公共成员组成的类型。

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

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

aiazj4mn

aiazj4mn2#

关于您引用的链接的说明:
这是关于TypeScript代码样式的文档,而不是如何实现项目的样式指南。
如果使用I前缀对您和您的团队有意义,请使用它(我这样做)。
如果没有,也许Java风格的SomeThing(接口)与SomeThingImpl(实现),那么无论如何都要使用它。

h9a6wy2h

h9a6wy2h3#

我发现**@stanislav-berkov**是对OP问题的一个很好的回答。我只想分享我的两分钱,并补充说,最后,这取决于你的团队/部门/公司/无论什么,要达成共识,并制定自己的规则/指导方针。
只要有可能和需要,坚持标准和/或惯例是一个很好的实践,它使事情更容易理解。另一方面,我确实喜欢认为我们仍然可以自由选择如何编写代码。
从情感的Angular 来看,我们写代码的方式,或者说我们的编码风格,反映了我们的个性,在某些情况下甚至反映了我们的情绪。这就是让我们人类而不仅仅是编码机器遵守规则的原因。我相信编码可以是一门手艺,而不仅仅是一个工业化的过程。

uqjltbpv

uqjltbpv4#

我个人很喜欢把一个名词变成一个形容词,加上后缀-able。这听起来很不合适,但我喜欢它!

interface Walletable {
  inPocket:boolean
  cash:number
}

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

}

vulvrdjw

vulvrdjw5#

Typescript文档中建议的指南不是针对使用Typescript的人,而是针对为Typescript项目做出贡献的人。如果您阅读了页面开头的详细信息,它明确定义了谁应该使用该指南。这里是指南的链接。Typescript guidelines
总之,作为一个开发人员,你可以用你认为合适的方式来命名你的接口。

rlcwz9us

rlcwz9us6#

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

export interface Engine {
  rpm: number;
}

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

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

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

j2datikz

j2datikz7#

我喜欢添加Props后缀。

interface FormProps {
 some: string;
}

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

dsf9zpds8#

接口的类型是实现细节。实现细节应该隐藏在API:s中。这就是为什么你应该避免I
您应该避免prefixsuffix。这两个都是错误的:

  • ICar
  • CarInterface

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

  • Car-在API中公开的接口。
  • CarImpl-API的实现,对使用者隐藏。
5jdjgkvh

5jdjgkvh9#

如果您想实现一个干净的架构,在某些情况下,当然在我看来,使用I前缀确实是有意义的...
例如,假设您希望所有业务对象都是可审计的(即具有createdBy、createdAt等字段)。
您可能会在您的域包中结束:

export interface MyInterface {
     createdDate : Date;
     ....
}

export class AuditableModel implements MyInterface{
     createdDate : Date;
     ....
}

在您的基础设施包中,您可能会有:

export class AuditableDbEntity implements MyInterface {
    @Property()
    createdDate: Date = new Date();
    ...
}

那么MyInterface应该被命名为什么呢?在我看来,IAuditable是一个很好的名字。我正在努力寻找一个更有意义的名字。
这与在域中定义服务的情况不同:

export interface Logger {
    log(msg:string)
}

在您的基础设施中,将在域中注入一个具体的实现:

export class ConsoleLogger implements Logger {
      log(msg:string){
           console.log(msg)
      }
}

在这里,接口是一个直接的“Logger”迫使实现者为实现找到一个更好的名称(即ConsoleLogger而不仅仅是Logger)
最后,为了保持连贯性,我在所有地方都使用I前缀。

相关问题