【问题标题】:How to use pointers to interface in go as internal data structure for collection-like types?如何使用指向接口的指针作为类集合类型的内部数据结构?
【发布时间】:2021-10-01 07:17:50
【问题描述】:

TLDR; 我需要使用指向接口的指针,例如*Comparable 作为Data 的内部对象在我的Node 中作为Tree 的一部分。 Data 应该符合指向Comparable 接口的指针,该接口只有一个方法Less,它比较Comparable 接口类型的两个对象。

如果我在Node 中只使用Comparable 作为Data 的类型,没有指针,一切正常。然而,当我切换到*Comparable 后,编译器给出了奇怪的错误并且代码无法编译

代码Comparable:

package main

type Comparable interface {
    Less(than Comparable) bool
}

type Node struct {
    Data Comparable
}

func NewNode(data Comparable) *Node {
    return &Node{Data: data} }

type Tree struct {
    root *Node
}

func NewTree() *Tree {
    return &Tree{}
}

func (t *Tree) insert(data Comparable) {
    if t.root == nil || t.root.Data.Less(data) {
        t.root = NewNode(data)
    }
}

type Row struct {
    Row []string
}

func NewRow(row[] string) *Row {
    return &Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
    return r.Row[0] < other.(Row).Row[0]
}

func main() {
    t := NewTree()
    t.insert(*NewRow([]string{"123"}))
    fmt.Printf("%v\n", t.root.Data.(Row).Row)
}

测试:

package main

import (
    "reflect"
    "testing"
)

func TestInsert(t *testing.T) {
    d := []string{"42"}
    tree := NewTree()
    tree.insert(*NewRow(d))
    if !reflect.DeepEqual(tree.root.Data.(Row).Row, d) {
        t.Error("returned elements are not matching")
    }
}

func TestInsert2x(t *testing.T) {
    d1 := []string{"42"}
    d2 := []string{"99"}
    tree := NewTree()
    tree.insert(*NewRow(d1))
    tree.insert(*NewRow(d2))
    if !reflect.DeepEqual(tree.root.Data.(Row).Row, d2) {
        t.Error("returned elements are not matching")
    }
}

但是,当我将 Comparable 转换为 *Comparable 时,一切都会中断,根本原因是 *Row is not equal to *Comparable 编译器错误:

对于线return r.Row[0] &lt; other.(*Row).Row[0]

无效类型断言:other.(*Row)(非接口类型 *Comparable on left)

对于线if t.root == nil || t.root.Data.Less(data) {

未解决的参考“少”

package main

type Comparable interface {
    Less(than *Comparable) bool
}

type Node struct {
    Data *Comparable
}

func NewNode(data *Comparable) *Node {
    return &Node{Data: data}
}

type Tree struct {
    root *Node
}

func NewTree() *Tree {
    return &Tree{}
}

func (t *Tree) insert(data *Comparable) {
    if t.root == nil || t.root.Data.Less(data) {
        t.root = NewNode(data)
    }
}

type Row struct {
    Row []string
}

func NewRow(row[] string) *Row {
    return &Row{Row: row}
}

func (r *Row) Less(other *Comparable) bool {
    return r.Row[0] < other.(*Row).Row[0]
}

func main() {
    t := NewTree()
    t.insert(NewRow([]string{"123"}))
    fmt.Printf("%v\n", t.root.Data.(*Row).Row)
}

测试:

package main

import (
    "reflect"
    "testing"
)

func TestInsert(t *testing.T) {
    d := []string{"42"}
    tree := NewTree()
    tree.insert(NewRow(d))
    if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d) {
        t.Error("returned elements are not matching")
    }
}

func TestInsert2x(t *testing.T) {
    d1 := []string{"42"}
    d2 := []string{"99"}
    tree := NewTree()
    tree.insert(NewRow(d1))
    tree.insert(NewRow(d2))
    if !reflect.DeepEqual(tree.root.Data.(*Row).Row, d2) {
        t.Error("returned elements are not matching")
    }
}

问题是如何在Node 中使用*Comparable 作为Data 的类型,以便上面的代码编译。我尝试了几个丑陋的选项,interface{} 类型为Data,并且到处都是显式转换。我不喜欢这种方法,因为它非常不安全。

您可以查看my github repo中的所有代码部分

【问题讨论】:

  • 为什么要使用指向接口的指针?在 Go 中,它们几乎从来都不是一个好主意。正如您所发现的,接口已经可以保存指向结构的指针。您说使用常规的Comparable 界面“工作正常”,那您为什么要更改它?
  • 如果我只在Node 中使用Comparable,这意味着所有Data 都被Node 复制,当我将它传递给函数或者当我复制、构造或重新创建一棵树时.我想在我的代码中逃避这种应对。
  • 用例:您已经在某处创建了巨大的type Row struct { Row string } 对象(而不是上面代码中的slice of strings,只是一个string)。并且您想不惜一切代价避免复制Row 字符串。

标签: go templates interface


【解决方案1】:

接口可以接受结构和指向结构的指针。

您可以将第一个 Tree.insert(data Comparable) 函数定义与第二个 main() 函数调用组合起来,将指向行 (*Row) 的指针传递给 Tree.insert(data Comparable)

这是一个完整的运行示例,也在 Go Playground 上:https://play.golang.org/p/Gh_RT3R-Fy0

package main

import (
    "fmt"
)

type Comparable interface {
    Less(than Comparable) bool
}

type Node struct {
    Data Comparable
}

func NewNode(data Comparable) *Node {
    return &Node{Data: data}
}

type Tree struct {
    root *Node
}

func NewTree() *Tree {
    return &Tree{}
}

func (t *Tree) insert(data Comparable) {
    if t.root == nil || t.root.Data.Less(data) {
        t.root = NewNode(data)
    }
}

type Row struct {
    Row []string
}

func NewRow(row []string) *Row {
    return &Row{Row: row}
}

func (r Row) Less(other Comparable) bool {
    return r.Row[0] < other.(*Row).Row[0]
}

func main() {
    t := NewTree()
    t.insert(NewRow([]string{"123"}))
    fmt.Printf("%v\n", t.root.Data.(*Row).Row)
}

话虽如此,这是一个脆弱的代码,因为Row.Less 函数访问Comparable 的底层类型并假设数组的索引值为0(长度> 0)。

func (r Row) Less(other Comparable) bool {
    return r.Row[0] < other.(*Row).Row[0]
}

在调用接口函数时,最好删除对Comparable 底层类型的任何依赖。您可以按如下方式扩充Comparable 接口和Row.Less 定义。在这种方法中,不需要在 Row 结构定义中进行强制转换。

On Go Playground:https://play.golang.org/p/8-71-pEn-zK

type Comparable interface {
    Less(than Comparable) bool
    CompareValue() string
}

func (r Row) CompareValue() string {
    if len(r.Row) == 0 {
        return ""
    }
    return r.Row[0]
}

func (r Row) Less(other Comparable) bool {
    return r.CompareValue() < other.CompareValue()
}

有关接口指针的更多信息,请参阅使用接口指针的 Go 常见问题解答:

https://golang.org/doc/faq#pointer_to_interface

什么时候应该使用指向接口的指针?

几乎没有。指向接口值的指针仅在涉及伪装接口值类型以进行延迟评估的罕见且棘手的情况下出现。

将指向接口值的指针传递给期望接口的函数是一个常见的错误。编译器会抱怨这个错误,但情况仍然会令人困惑,因为有时会出现pointer is necessary to satisfy an interface。洞察力是,虽然指向具体类型的指针可以满足接口,但有一个例外,指向接口的指针永远不能满足接口。

[...]

一个例外是任何值,甚至是指向接口的指针,都可以分配给空接口类型的变量 (interface{})。即便如此,如果值是指向接口的指针,那几乎肯定是错误的。结果可能会令人困惑。

【讨论】:

  • 谢谢回复!上面的代码需要在 Less func 中修复:func (r *Row) Less(other Comparable) bool { return r.Row[0] &lt; other.(*Row).Row[0] }。例如。 play.golang.org/p/VfJrt53HO8L。有什么方法可以捕捉到这些错误吗?这个转换为 *Row:.(*Row) 看起来很不安全。有没有办法完全摆脱它?
  • 谢谢。我修复了Less 函数,但正如您所注意到的,这段代码很脆弱。问题不在于指针,而是您正在转换为接口未定义的内容。在Row.Less 函数中转换为Row, .(Row) 同样不安全,因为您假设other Comparable 的值是Row,在这种情况下您根本不需要使用接口。为了使Comparable 接口更安全,您可以使用Comparable.CompareValue() 之类的另一个函数来扩充接口,该函数返回一个值以比较我添加到答案中的值。
猜你喜欢
  • 2019-05-04
  • 2020-10-25
  • 2021-10-16
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
  • 2019-03-18
  • 2021-04-01
  • 2016-08-01
相关资源
最近更新 更多