首页 > 解决方案 > 何时使用可区分联合与实现接口的类

问题描述

我有这样的代码:

interface Node{
    value: number;
}

class Parent implements Node{
    children: Node[] = [];
    value: number = 0;
}


class Leaf implements Node{
    value: number = 0;
}


function test(a:Node) : void {
  if(a instanceof Leaf)
    console.log("Leaf")
  else 
    console.log("Parent")
}

查看https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions,似乎实现这一目标的另一种方式是

type Parent = {kind: "Parent", value:number, children:(A|B)[]}
type Leaf = {kind: "Leaf", value:number}

type Node = Parent | Leaf

function test(a:Node) :void {
  if(a.kind === "Leaf")
    console.log("yes")
  else 
    console.log("no")
}

现在,我对使用什么感到困惑。到目前为止,我使用的所有其他语言都只有其中一个选项 - 这里是 typescript 两者都有。除了方法 1 有一个构造函数之外,方法 2 完全被转译了,这里真正的区别是什么?在代码库中最好看到哪种方法?

如您所见,该功能使我们可以轻松地遍历树。当然,我也可以只有一个类型/类并将子项设置为 [] 那里,但是类型与类的问题被重复了。有人告诉我,不同的课程对性能更友好。

标签: typescripttypescript-typingsdiscriminated-union

解决方案


可区分联合和接口之间的主要区别在于接口对扩展是开放的,但联合不是。

也就是说,给定一个接口,任何人都可以添加该接口的新实现。相反,向联合中添加其他类型需要更改该联合。因此,如果预先知道可能的类型集,则联合是优越的,而接口允许稍后扩展可能的类型集。

至于类和接口之间的选择,类有一个原型,这允许它们继承行为(甚至状态),但会使类型更难通过网络发送(JSON 没有原型的概念......)。因此,如果您从原型中受益,那么类会受到青睐,而接口(或联合)则更适合与其他进程交换数据。

不过,在您的情况下,所有这些都是一个红鲱鱼,因为我认为用不同类型表示树中的不同节点没有任何好处。您的行为没有差异,数据差异最容易通过空列表建模。也就是说,我只是这样做:

interface Node {
  value: number;
  children: node[];
}

这使代码变得更简单。假设您要向叶子添加一个新子节点。在您的情况下,您需要执行以下操作:

addChild(value: number) {
  const leaf = new Leaf();
  leaf.value = value;

  const newSelf = new Parent();
  newSelf.value = this.value;
  newSelf.children = [leaf];

  this.parent.children = this.parent.children.map(child => child == this ? newSelf : child);
}

而我只会这样做:

addChild(value: number) {
  this.children.push({
    value, 
    children: []
  });
}

有人告诉我,不同的课程对性能更友好。

差异非常小(例如,大约 0.000000001 秒)。你可能有更紧迫的担忧。


推荐阅读