什么是使用类和接口之间的区别?

时间:2017-07-30 20:50:43

标签: javascript json angular typescript structural-typing

这样做有什么区别

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()
}

2 个答案:

答案 0 :(得分:10)

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

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

请注意,在这个答案中,我没有使用Comment.likes的类型,因为在问题中你将它作为string,但对我来说感觉就像number ,所以我把它留给了读者。

JavaScript和TypeScript中的类声明:

在JavaScript中,一个类声明

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

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

在TypeScript中,类声明会创建两个事物。

第一个是与上述完全相同的JavaScript类。 第二个是类型,它描述了通过编写

创建的实例的结构
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() {
  const [topComment] = this.comments.slice().sort((x, y) => y < x);
  // since there might not be any comments, it is likely that a check will be made here
  if (topComment instanceof Comment) { // always false at runtime
    return topComment;
  }
}

由于注释实际上不是Comment类的实例,因此上述检查将始终失败,因此代码中存在错误。但是,typescript不会捕获错误,因为我们说commentsComment类的一个实例数组,这将使检查有效(回想一下response.json()返回{{1可以在没有警告的情况下转换为任何类型,因此在编译时一切正常。

但是,如果我们已将评论声明为any

interface

然后interface Comment { comment: string; likes; } 将继续键入check,因为它实际上是正确的代码,但getComments将在编译时在if语句中引发错误,因为正如许多其他人所指出的那样getTopComment作为仅编译时构造的{1}}不能用作执行interface检查的构造函数。编译器会告诉我们我们有错误。

<强>说明:

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

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

考虑

instanceof

如果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, comment: string) { const commentService = new CommentService(); await commentService.createCommnet({comment, likes}); } 是一个类,我需要通过Comment的{​​{1}}来引入一些样板。当然,这也增加了耦合。

import

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

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

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来描述响应的类型以及使用TypeScript时与HTTP服务交互的请求。

注意:您还可以使用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 (Number(likes) < 0 || !Number.isSafeInteger(Number(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周围的工具通常会使其更受欢迎场景。

答案 1 :(得分:7)

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

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

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