使用类和接口有啥区别?

Posted

技术标签:

【中文标题】使用类和接口有啥区别?【英文标题】:Whats the difference between using a class and interface?使用类和接口有什么区别? 【发布时间】:2018-01-06 08:35:20 【问题描述】:

这样做有什么区别

export class Comment 
  likes: string;
  comment: string;

  constructor(likes: string, comment: string)
    this.comment = comment;
    this.likes = likes;
  

还有这个

export interface CommentInterface 
  likes: string;
  comment: string;

关于声明可观察类型

register: Observable<CommentInterface[]> 
    return this.http.get()

【问题讨论】:

http.post() 永远不会返回您的类的实例。因为 http 所做的只是将正文解析为 JSON,从而生成基本的 javascript 对象。它不会实例化您的类。 对于 http.get() 也是如此。它获取响应正文并将其解析为 JSON;这不会产生任何类的实例。 第二个 sn-p 不是接口:export class LoginInterface。这是一堂课。 我认为你的意思是'export interface LoginInterface',不是吗,不是'export class LoginInterface',否则你的例子都是一样的。 一个接口在编译时被擦除,并且只在类型检查期间产生影响。类是一个实际的运行时工件,您可以在其中实现具体的方法和属性。 【参考方案1】:

正如 JB Nizet 非常正确地指出的那样,由 HTTP 请求产生的反序列化 JSON 值永远不会是类的实例。

虽然 TypeScript 中 class 构造的双重角色(见下文)使得使用 class 来描述这些响应值的形状成为可能,但这是一种不好的做法,因为响应文本将被反序列化为纯 JavaScript 对象。

JavaScript 和 TypeScript 中的类声明:

在 JavaScript 中,类声明

class Comment 
  constructor(likes, comment) 
    this.likes = likes;
    this.comment = comment;
  

创建一个可以使用new 实例化的值,以充当本质上的工厂。

在 TypeScript 中,类声明创建 两个 东西。

第一个是与上面描述的完全相同的 JavaScript 类 value。 第二个是type,描述了通过编写创建的实例的结构

new Comment(4, 'I love your essays')

第二个工件,类型,然后可以用作类型注释,例如在您的示例中

register(): Observable<Comment[]> 
    return this.http.get()

这表示register 返回ObservableComment class 实例数组。

现在假设您的 HTTP 请求返回以下 JSON

[
  
    "likes": 4,
    "comment": "I love you oh so very much"
  ,
  
    "likes": 1,
    "comment": "I lust after that feeling of approval that only likes can bring"
  
]

但是方法声明

register(): Observable<Comment[]>;

虽然它正确地允许调用者写

register().subscribe(comments => 
  for (const comment of comment) 
    if (comment.likes > 0) 
      likedComments.push(comment);
    
  
);

这一切都很好,不幸的是它还允许调用者编写类似的代码

getComments() 
  register().subscribe(comments => 
    this.comments = comments;
  );


getTopComment() 
  // since there might not be any comments
  // it is likely that a check will be made here
  const [topComment] = this.comments.slice().sort((x, y) => y - x);

  // BUG! Always false at runtime.
  if (topComment instanceof Comment) 
    return topComment;
  

由于 cmets 实际上不是 Comment 类的实例,因此上述检查将始终失败,因此代码中存在错误。但是,TypeScript 不会捕获错误,因为我们说过 commentsComment 类的实例数组,这将使检查有效(回想一下 response.json() 返回 any 可以转换为任何输入没有警告,所以在编译时一切看起来都很好)。

如果我们将评论声明为interface

interface Comment 
  comment: string;
  likes: number;

然后getComments 将继续进行类型检查,因为它实际上是正确的代码,但getTopComment 会在编译时在 if 语句中引发错误,因为正如许多其他人所指出的那样,interface 是仅编译时构造,不能像构造函数一样用于执行instanceof 检查。编译器会告诉我们有错误。


备注:

除了给出的所有其他原因,在我看来,当你有一些东西代表 JavaScript/TypeScript 中的普通旧数据时,使用类通常是矫枉过正的。它创建了一个带有原型的函数,并有许多我们可能不需要或关心的其他方面。

如果你使用对象,它也会丢掉你默认获得的好处。这些好处包括用于创建和复制对象的语法糖以及 TypeScript 对这些对象类型的推断。

考虑

import Comment from 'app/comment';

export default class CommentService     
  
  async getComments(): Promse<Array<Comment>> 
    const response = await fetch('api/comments', httpMethod: 'GET');
    const comments = await response.json();
    return comments as Comment[]; // just being explicit.
  

  async createComment(comment: Comment): Promise<Comment> 
    const response = await fetch('api/comments', 
      httpMethod: 'POST',
      body: JSON.stringify(comment)
    );
    const result = await response.json();
    return result as Comment; // just being explicit.
  

如果Comment是一个接口,我想使用上面的服务来创建评论,我可以这样做

import CommentService from 'app/comment-service';

export async function createComment(likes: number, comment: string)     
  const commentService = new CommentService();
  
  await commentService.createCommnet(comment, likes);

如果Comment 是一个类,我需要通过需要importComment 来引入一些样板。当然,这也会增加耦合度。

import CommentService from 'app/comment-service';
import Comment from 'app/comment';

export async function createComment(likes, comment: string)     
  const commentService = new CommentService();
  
  const comment = new Comment(likes, comment); // better get the order right

  await commentService.createCommnet(comment);

这是额外的两行,其中一个涉及依赖另一个模块来创建一个对象。

现在,如果 Comment 是一个接口,但我想要一个复杂的类来进行验证等等我将它提供给我的服务之前,我仍然可以拥有它。

import CommentService from 'app/comment-service';
import Comment from 'app/comment';

// implements is optional and typescript will verify that this class implements Comment
// by looking at the definition of the service method so I could remove it and 
// also remove the import statement if I wish
class ValidatedComment implements Comment 
  constructor(public likes, public comment: string) 
    if (likes < 0 || !Number.isSafeInteger(likes)) 
      throw RangeError('Likes must be a valid number >= 0'
    
  


export async function createComment(likes, comment: string) 
  const commentService = new CommentService();
  
  const comment = new ValidatedComment(likes, comment); // better get the order right

  await commentService.createCommnet(comment);

简而言之,使用 interface 来描述响应的类型以及使用 TypeScript 时与 HTTP 服务交互的请求有很多原因。

注意:您也可以使用type 声明,它同样安全且健壮,但不太习惯用语,并且interface 周围的工具通常更适合这种情况。 p>

【讨论】:

【参考方案2】:

与大多数其他 OOP 语言一样:对于类,您可以创建实例(通过它们的构造函数),而您不能创建接口实例。

换句话说:如果你只是返回反序列化的 JSON,那么使用接口来避免混淆是有意义的。假设您将一些方法 foo 添加到您的 Comment 类中。如果您的register 方法被声明为返回Comment,那么您可能会假设您可以在寄存器的返回值上调用foo。但这不会起作用,因为 register 有效返回的只是反序列化的 JSON,而您在 Comment 类上没有实现 foo。更具体地说,它不是您的 Comment 类的实例。当然,您也可能不小心在您的CommentInterface 中声明了foo 方法,但它仍然无法工作,但是没有实际代码用于foo 方法,只是没有被执行,使它更容易推理您致电foo 不起作用的根本原因。

另外从语义层面考虑:声明返回接口保证了在接口上声明的所有内容都存在于返回值中。声明返回一个类实例保证你......嗯......返回一个类实例,这不是你正在做的,因为你正在返回反序列化的 Json。

【讨论】:

不错的 +1。可能值得一提的是instanceof 危险,这是许多人掉入的陷阱。

以上是关于使用类和接口有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

java中接口和类有啥区别 java中接口和类有啥区别

java接口与抽象类有啥区别?

java中接口和类有啥区别java中接口和类有啥区别

Java 8 中的抽象类和接口到底有啥区别?

java接口和类有啥区别?

C# 中的 Icompare 接口和 IComparable 接口有啥区别??