问题 Go的工会最佳实践


Go没有工会。但工会在许多地方都是必要的。 XML过度使用了联合或选择类型。我试图找出,这是解决失踪工会的首选方法。作为一个例子,我试图为非终端编写Go代码 Misc 在里面 XML标准 这可以是一个 评论, 一个 处理指令 要么 白色空间

编写三种基本类型的代码非常简单。它们映射到字符数组和结构。

type Comment Chars

type ProcessingInstruction struct {
    Target *Chars
    Data *Chars
}

type WhiteSpace Chars

但是当我完成了联合的代码时,它会因为许多冗余函数而变得非常繁琐。显然必须有一个容器结构。

type Misc struct {
    value interface {}
}

为了确保容器只保存三个允许的类型,我将值设为私有,并且我必须为每个类型编写一个构造函数。

func MiscComment(c *Comment) *Misc {
    return &Misc{c}
}

func MiscProcessingInstruction (pi *ProcessingInstruction) *Misc {
    return &Misc{pi}
}

func MiscWhiteSpace (ws *WhiteSpace) *Misc {
    return &Misc{ws}
}

为了能够测试union的内容,有必要编写三个谓词:

func (m Misc) IsComment () bool {
    _, itis := m.value.(*Comment)
    return itis
}

func (m Misc) IsProcessingInstruction () bool {
    _, itis := m.value.(*ProcessingInstruction)
    return itis
}

func (m Misc) IsWhiteSpace () bool {
    _, itis := m.value.(*WhiteSpace)
    return itis
}

并且为了获得正确类型的元素,有必要编写三个getter。

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

func (m Misc) ProcessingInstruction () *ProcessingInstruction {
    return m.value.(*ProcessingInstruction)
}

func (m Misc) WhiteSpace () *WhiteSpace {
    return m.value.(*WhiteSpace)
}

在此之后,我能够创建一个数组 Misc 类型并使用它:

func main () {

    miscs := []*Misc{
        MiscComment((*Comment)(NewChars("comment"))),
        MiscProcessingInstruction(&ProcessingInstruction{
            NewChars("target"),
            NewChars("data")}),
        MiscWhiteSpace((*WhiteSpace)(NewChars(" \n")))}

    for _, misc := range miscs {
        if (misc.IsComment()) {
            fmt.Println ((*Chars)(misc.Comment()))
        } else if (misc.IsProcessingInstruction()) {
            fmt.Println (*misc.ProcessingInstruction())
        } else if (misc.IsWhiteSpace()) {
            fmt.Println ((*Chars)(misc.WhiteSpace()))
        } else {
            panic ("invalid misc");
        }
    }
}

你看到有很多代码看起来几乎一样。任何其他工会都会如此。所以我的问题是:有没有办法简化在Go中处理工会的方式?

去声明简化编程工作 删除冗余。但我认为上面的例子显示了完全相反的情况。我错过了什么吗?

这是完整的例子: http://play.golang.org/p/Zv8rYX-aFr


6856
2018-02-04 13:06


起源



答案:


因为你似乎在问,因为你想要类型安全,我首先会争辩你的初始 解决方案已经不安全了

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

如果你没有检查过会会引起恐慌 IsComment 之前。因此,这种解决方案没有任何好处 提出的类型开关 沃尔克

由于您想要对代码进行分组,因此您可以编写一个函数来确定代码 Misc 元素是:

func IsMisc(v {}interface) bool {
    switch v.(type) {
        case Comment: return true
        // ...
    }
}

但是,这也不会给你带来编译器类型检查。

如果你想能够识别出某些东西 Misc 然后你应该由编译器 考虑创建一个标记为的东西的界面 Misc

type Misc interface {
    ImplementsMisc()
}

type Comment Chars
func (c Comment) ImplementsMisc() {}

type ProcessingInstruction
func (p ProcessingInstruction) ImplementsMisc() {}

这样你就可以编写只处理misc的函数。对象并在以后做出决定 你真正想要处理的这些功能(评论,说明......)

如果你想模仿工会,那么你写它的方式就是我所知道的方式。


7
2018-02-05 01:43





我认为这些代码量可能会减少,例如我个人认为不保障 type Misc 反对包含“非法”的东西真的很有用:简单 type Misc interface{} 会这样做,还是?

这样你就可以省去构造者和所有人 Is{Comment,ProcessingInstruction,WhiteSpace} 方法归结为类型开关

switch m := misc.(type) {
    Comment: fmt.Println(m)
    ... 
    default: panic()
}

这是什么包编码/ xml与Token。


5
2018-02-04 14:20



如果我把所有东西都存入 interface{} 我可以前往 方案。如果使用类型,则使用静态类型语言才有意义。 - ceving
如果您使用非空接口,那么它将限制可以存储在其中的内容,类似于您对其所做的操作 Misc 结构。如果接口需要在其签名中使用非导出类型的方法,则可以合理地确定它仅包含由包定义的类型。您还可以使用该界面来公开您正在处理的三种类型的常用功能。 - James Henstridge


答案:


因为你似乎在问,因为你想要类型安全,我首先会争辩你的初始 解决方案已经不安全了

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

如果你没有检查过会会引起恐慌 IsComment 之前。因此,这种解决方案没有任何好处 提出的类型开关 沃尔克

由于您想要对代码进行分组,因此您可以编写一个函数来确定代码 Misc 元素是:

func IsMisc(v {}interface) bool {
    switch v.(type) {
        case Comment: return true
        // ...
    }
}

但是,这也不会给你带来编译器类型检查。

如果你想能够识别出某些东西 Misc 然后你应该由编译器 考虑创建一个标记为的东西的界面 Misc

type Misc interface {
    ImplementsMisc()
}

type Comment Chars
func (c Comment) ImplementsMisc() {}

type ProcessingInstruction
func (p ProcessingInstruction) ImplementsMisc() {}

这样你就可以编写只处理misc的函数。对象并在以后做出决定 你真正想要处理的这些功能(评论,说明......)

如果你想模仿工会,那么你写它的方式就是我所知道的方式。


7
2018-02-05 01:43





我认为这些代码量可能会减少,例如我个人认为不保障 type Misc 反对包含“非法”的东西真的很有用:简单 type Misc interface{} 会这样做,还是?

这样你就可以省去构造者和所有人 Is{Comment,ProcessingInstruction,WhiteSpace} 方法归结为类型开关

switch m := misc.(type) {
    Comment: fmt.Println(m)
    ... 
    default: panic()
}

这是什么包编码/ xml与Token。


5
2018-02-04 14:20



如果我把所有东西都存入 interface{} 我可以前往 方案。如果使用类型,则使用静态类型语言才有意义。 - ceving
如果您使用非空接口,那么它将限制可以存储在其中的内容,类似于您对其所做的操作 Misc 结构。如果接口需要在其签名中使用非导出类型的方法,则可以合理地确定它仅包含由包定义的类型。您还可以使用该界面来公开您正在处理的三种类型的常用功能。 - James Henstridge


我不确定你理解你的问题。 “简单”的方法就像带有接口{}的encoding / xml包。如果您不想使用接口,那么您可以像您一样做。 但是,正如您所说,Go是一种打字语言,因此应该用于打字需求。 如果你有一个结构化的XML,那么Go可能非常合适,但你需要编写你的模式。如果你想要一个可变参数模式(一个给定的字段可以有多种类型),那么使用非类型语言可能会更好。

非常有用的json工具,可以轻松地为xml重写: http://mholt.github.io/json-to-go/

你给了一个json输入,它给你准确的Go结构。您可以有多种类型,但您需要知道哪个字段具有哪种类型。如果你不这样做,你需要使用反射,确实你失去了Go的很多兴趣。


1
2018-02-04 23:34





TL; DR 你不需要工会, interface{} 更好地解决了这个问题

C中的联合用于访问特殊内存/硬件。他们也 颠覆 类型系统。 Go没有语言原语访问特殊的内存/硬件,它也避开了 volatile 和位字段出于同样的原因。

在C / C ++中,联合也可用于真正的低级优化/位打包。权衡:牺牲类型系统并增加复杂性,有利于节省一些比特。这当然伴随着有关优化的所有警告。

Imagine Go有一个原生联盟类型。代码怎么会更好?用这个重写代码:

// pretend this struct was a union
type MiscUnion struct {
  c *Comment
  pi *ProcessingInstruction
  ws *WhiteSpace
}

即使有内置联盟访问成员 MiscUnion 需要某种运行时检查。所以使用界面并没有变得更糟。可以说是 interface 因为运行时类型检查是内置的(不可能出错)并且具有非常好的语法来处理它,所以是优越的。

联合类型的一个优点是静态类型检查,以确保只放入适当的具体类型 Misc。解决这个问题的Go方法是“New ...”函数,例如 MiscCommentMiscProcessingInstructionMiscWhiteSpace

这是一个清理过的示例 interface{} 和 New* 功能: http://play.golang.org/p/d5bC8mZAB_


-2
2018-02-06 18:03



这个问题 Misc 在你的例子中,它没有提供类型安全。目的是,Misc的内容必须只是注释处理指令或空白区域。但这并不是由语言强制执行的。相反,创建此misc也是有效的: []Misc{"kick", "ass", "this", "meaningless", "type"]。我所谓的工会不是确切的C联盟,而是一个 “选择类型”, “和型” 要么 “变种类型”。 - ceving
哇!我真的很喜欢写的 Go中的和类型。伟大的发现,我认为可能是你的问题的最佳答案。 - deft_code