何时使用区分联合与 类 实现接口

When to use Discriminated Unions vs Classes implementing an interface

我有这样的代码:

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

现在,我对使用什么感到困惑。到目前为止,我使用过的所有其他语言都只有其中一个选项——这里是带有这两个选项的打字稿。除了具有构造函数的方法 1 之外,虽然方法 2 已完全转换掉,但这里真正的区别是什么?在代码库中最好看到哪种方法?

如您所见,功能是让我们可以轻松地遍历一棵树。当然,我也可以只有一个 type/class 并将 children 设置为 [] ,但是类型与 class 的问题又重复了。我被告知拥有不同的 classes 对性能更友好。

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

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

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

不过,在您的情况下,所有这些都是转移注意力,因为我看不到用不同类型表示树中不同节点的任何好处。您在行为上没有差异,并且数据差异最容易通过空列表建模。也就是说,我会简单地做:

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

这使代码简单了很多。假设您要向叶子添加一个新的 child。在你的情况下,你需要做这样的事情:

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: []
  });
}

And I was told that having different classes was more performance friendly.

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