2017-07-30 57 views
6

是什么关系这样使用类和接口有什么区别?

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

http.post()永远不会返回你的类的一个实例。因为所有的http都会将正文解析为JSON,这会生成基本的JavaScript对象。它不会实例化您的类。 –

+0

我觉得这个问题很清楚,不应该关闭。为什么?因为Angular文档中充斥着使用'class'而不是'interface'的例子。在声明XHR响应时,“interface”是正确的选择。 –

+0

我只是用它作为例子。我改变它得到和登录,所以它更有意义 – Anonguy123

回答

7

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

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

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

+1

不错的+1。值得一提的是'instanceof'危险,这是很多人陷入的陷阱。 –

9

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

虽然打字稿的class构建的双重作用(见下文)使得有可能使用一个class描述这些响应值的形状,它是一种较差的做法,因为所述响应文本将被反序列化为普通的JavaScript对象。

注意,在整个这个答案,因为在这个问题你有它作为一个string,但感觉像一个number给我,所以我把它留给读者,我不使用类型Comment.likes

类声明JavaScript和打字稿:

在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返回Commentclass实例的数组的Observable

现在,想象你的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()返回any,它可以转换为任何类型而没有警告,因此一切都很正常编译时间。

但是如果我们宣布评论为interface

interface Comment { 
    comment: string; 
    likes; 
} 

然后getComments将继续类型检查,因为它实际上是正确的代码,但getTopComment将在编译时产生错误if语句是因为,如前所述b y许多其他人,仅作为编译时构造的interface不能用作执行instanceof检查的构造函数。编译器会告诉我们我们有一个错误。


备注:

除了考虑到所有其他的原因,在我看来,当你有一些代表普通的旧数据的JavaScript /打字稿,使用类通常是矫枉过正。它用原型创建了一个函数,并且有一些我们不太需要或关心的其他方面。

它还会抛弃您在使用对象时默认获得的好处。这些好处包括用于创建和复制对象的语法糖以及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, comment: string) {  
    const commentService = new CommentService(); 

    await commentService.createCommnet({comment, likes}); 
} 

如果Comment是一类,我需要通过Commentimport来引入一些锅炉板。当然,这也增加了耦合。

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 (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来形容答复以及使用打字稿时与HTTP服务交互的请求的类型。

注:你也可以使用一个type声明,这同样是安全可靠的,但它是不太习惯和周围interface模具往往使优选该方案。

相关问题