何时使用区分联合与 类 实现接口
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 秒)。您可能有更紧迫的担忧。
我有这样的代码:
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 秒)。您可能有更紧迫的担忧。